n8n-nodes-vercel-ai-sdk-universal-temp 0.1.54 → 0.1.56
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
|
-
const client = await getGoogleCacheManager(apiKey);
|
|
208
|
-
await client.caches.delete(cacheName);
|
|
132
|
+
const normalizedCacheContent = (_a = cacheContent === null || cacheContent === void 0 ? void 0 : cacheContent.trim()) !== null && _a !== void 0 ? _a : '';
|
|
133
|
+
if (!normalizedCacheContent) {
|
|
134
|
+
return null;
|
|
209
135
|
}
|
|
210
|
-
|
|
211
|
-
|
|
136
|
+
const googleCacheManager = await getGoogleCacheManager(apiKey);
|
|
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, Object.keys(cacheKeyData).sort());
|
|
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('Cache creation returned no name identifier');
|
|
167
|
+
}
|
|
168
|
+
googleCachedContexts.set(cacheKey, { name: cachedContentName }, (ttlSeconds - 300) * 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: Google cache creation failed:', 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
|
-
sections.push(`
|
|
230
|
+
sections.push(`Conversation History:\n${messageSections.join('\n\n')}`);
|
|
321
231
|
hasMessages = true;
|
|
322
232
|
}
|
|
323
233
|
}
|
|
324
|
-
if (
|
|
325
|
-
sections.push(`
|
|
234
|
+
if (input.prompt && input.prompt.trim()) {
|
|
235
|
+
sections.push(`Instruction 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
|
-
}
|
|
361
|
+
const batchResults = await Promise.all(batchPromises);
|
|
362
|
+
processedAttachments.push(...batchResults);
|
|
532
363
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
};
|
|
364
|
+
for (const attachment of processedAttachments) {
|
|
365
|
+
if (attachment) {
|
|
366
|
+
parts.push(attachment);
|
|
550
367
|
}
|
|
551
368
|
}
|
|
552
|
-
|
|
553
|
-
if (isLikelyBase64(fileContentInput)) {
|
|
554
|
-
const buffer = Buffer.from(fileContentInput, 'base64');
|
|
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
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
catch (error) {
|
|
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;
|
|
@@ -712,152 +539,155 @@ function formatResponse(result) {
|
|
|
712
539
|
headers: (_d = result.response) === null || _d === void 0 ? void 0 : _d.headers,
|
|
713
540
|
};
|
|
714
541
|
}
|
|
715
|
-
async function getProvider(provider,
|
|
716
|
-
const headersKey =
|
|
717
|
-
?
|
|
542
|
+
async function getProvider(provider, apiKey, baseURL, customHeaders) {
|
|
543
|
+
const headersKey = customHeaders
|
|
544
|
+
? JSON.stringify(Object.keys(customHeaders)
|
|
718
545
|
.sort()
|
|
719
|
-
.map((key) => [key,
|
|
546
|
+
.map((key) => [key, customHeaders[key]]))
|
|
720
547
|
: '';
|
|
721
|
-
const cacheKey =
|
|
548
|
+
const cacheKey = `${provider}:${apiKey}:${baseURL || ''}:${headersKey}`;
|
|
722
549
|
const cached = providerCache.get(cacheKey);
|
|
723
550
|
if (cached)
|
|
724
551
|
return cached;
|
|
552
|
+
let providerInstance;
|
|
725
553
|
try {
|
|
726
|
-
let providerInstance;
|
|
727
554
|
switch (provider) {
|
|
728
555
|
case 'google':
|
|
729
556
|
const { createGoogleGenerativeAI } = await Promise.resolve().then(() => __importStar(require('@ai-sdk/google')));
|
|
730
557
|
providerInstance = createGoogleGenerativeAI({
|
|
731
|
-
apiKey
|
|
732
|
-
...(
|
|
733
|
-
...(
|
|
734
|
-
headers: config.customHeaders
|
|
735
|
-
}),
|
|
558
|
+
apiKey,
|
|
559
|
+
...(baseURL && { baseURL }),
|
|
560
|
+
...(customHeaders && Object.keys(customHeaders).length > 0 && { headers: customHeaders }),
|
|
736
561
|
});
|
|
737
562
|
break;
|
|
738
563
|
case 'deepseek':
|
|
739
564
|
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
|
-
});
|
|
565
|
+
providerInstance = createDeepSeek({ apiKey, ...(baseURL && { baseURL }) });
|
|
744
566
|
break;
|
|
745
567
|
case 'groq':
|
|
746
568
|
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
|
-
});
|
|
569
|
+
providerInstance = createGroq({ apiKey, ...(baseURL && { baseURL }) });
|
|
751
570
|
break;
|
|
752
571
|
case 'openrouter':
|
|
753
572
|
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
|
-
});
|
|
573
|
+
providerInstance = createOpenRouter({ apiKey, ...(baseURL && { baseURL }) });
|
|
758
574
|
break;
|
|
759
575
|
default:
|
|
760
|
-
throw new
|
|
576
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
761
577
|
}
|
|
762
578
|
providerCache.set(cacheKey, providerInstance);
|
|
763
579
|
return providerInstance;
|
|
764
580
|
}
|
|
765
581
|
catch (error) {
|
|
766
|
-
throw new
|
|
582
|
+
throw new Error(`Failed to initialize ${provider} provider: ${error.message}`);
|
|
767
583
|
}
|
|
768
584
|
}
|
|
769
585
|
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:');
|
|
586
|
+
const cacheKey = `schema:${Buffer.from(rawSchema).toString('base64').substring(0, 50)}`;
|
|
777
587
|
const cached = schemaCache.get(cacheKey);
|
|
778
588
|
if (cached)
|
|
779
589
|
return cached;
|
|
590
|
+
let parsedSchema;
|
|
780
591
|
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;
|
|
592
|
+
parsedSchema = JSON.parse(rawSchema);
|
|
787
593
|
}
|
|
788
594
|
catch (err) {
|
|
789
|
-
throw new
|
|
595
|
+
throw new n8n_workflow_1.NodeOperationError(exec.getNode(), 'Schema is not valid JSON: ' + err.message);
|
|
596
|
+
}
|
|
597
|
+
if (!ajv.validateSchema(parsedSchema)) {
|
|
598
|
+
throw new n8n_workflow_1.NodeOperationError(exec.getNode(), `Invalid JSON Schema: ${ajv.errorsText(ajv.errors)}`);
|
|
790
599
|
}
|
|
600
|
+
schemaCache.set(cacheKey, parsedSchema);
|
|
601
|
+
return parsedSchema;
|
|
791
602
|
}
|
|
792
603
|
function parseStopSequences(stopSequencesStr) {
|
|
793
604
|
if (!stopSequencesStr)
|
|
794
605
|
return undefined;
|
|
795
|
-
|
|
796
|
-
.split(',')
|
|
797
|
-
.map(s => s.trim())
|
|
798
|
-
.filter(Boolean);
|
|
799
|
-
return sequences.length > 0 ? sequences : undefined;
|
|
606
|
+
return stopSequencesStr.split(',').map(s => s.trim()).filter(s => s.length > 0);
|
|
800
607
|
}
|
|
801
608
|
function applyNumericOptions(params, options, keys) {
|
|
802
609
|
for (const key of keys) {
|
|
803
610
|
const value = options[key];
|
|
804
611
|
if (value !== undefined && value !== null && value !== '') {
|
|
805
|
-
|
|
806
|
-
if (!isNaN(numValue)) {
|
|
807
|
-
params[key] = numValue;
|
|
808
|
-
}
|
|
612
|
+
params[key] = value;
|
|
809
613
|
}
|
|
810
614
|
}
|
|
811
615
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
616
|
+
class UniversalAI {
|
|
617
|
+
constructor() {
|
|
618
|
+
this.description = descriptions_1.UNIVERSAL_AI_DESCRIPTION;
|
|
619
|
+
this.methods = {
|
|
620
|
+
loadOptions: {
|
|
621
|
+
async getModels() {
|
|
622
|
+
const provider = this.getCurrentNodeParameter('provider');
|
|
623
|
+
const cacheKey = `models:${provider}`;
|
|
624
|
+
const cached = modelCache.get(cacheKey);
|
|
625
|
+
if (cached)
|
|
626
|
+
return cached;
|
|
627
|
+
const { OPENROUTER_MODELS, GOOGLE_GEMINI_MODELS, DEEPSEEK_MODELS, GROQ_MODELS } = await Promise.resolve().then(() => __importStar(require('./model-lists')));
|
|
628
|
+
const models = {
|
|
629
|
+
google: GOOGLE_GEMINI_MODELS,
|
|
630
|
+
deepseek: DEEPSEEK_MODELS,
|
|
631
|
+
groq: GROQ_MODELS,
|
|
632
|
+
openrouter: OPENROUTER_MODELS,
|
|
633
|
+
}[provider] || [];
|
|
634
|
+
modelCache.set(cacheKey, models);
|
|
635
|
+
return models;
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
};
|
|
825
639
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
640
|
+
async execute() {
|
|
641
|
+
const items = this.getInputData();
|
|
642
|
+
const returnData = [];
|
|
643
|
+
const provider = this.getNodeParameter('provider', 0);
|
|
644
|
+
const credentialType = {
|
|
645
|
+
google: 'googleGenerativeAIApi',
|
|
646
|
+
deepseek: 'deepSeekApi',
|
|
647
|
+
groq: 'groqApi',
|
|
648
|
+
openrouter: 'openRouterApi',
|
|
649
|
+
}[provider];
|
|
650
|
+
if (!credentialType) {
|
|
651
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unsupported provider: ${provider}`);
|
|
832
652
|
}
|
|
833
|
-
|
|
834
|
-
|
|
653
|
+
const credentials = await this.getCredentials(credentialType);
|
|
654
|
+
if (!(credentials === null || credentials === void 0 ? void 0 : credentials.apiKey)) {
|
|
655
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No API key provided in credentials');
|
|
835
656
|
}
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
success: false
|
|
657
|
+
const customHeaders = provider === 'google' ? getGoogleCustomHeaders(this, 0) : undefined;
|
|
658
|
+
const aiProvider = await getProvider(provider, credentials.apiKey, credentials.baseUrl, customHeaders);
|
|
659
|
+
for (let i = 0; i < items.length; i++) {
|
|
660
|
+
if (this.continueOnFail()) {
|
|
661
|
+
try {
|
|
662
|
+
const result = await processItem(this, i, provider, aiProvider, credentials.apiKey);
|
|
663
|
+
returnData.push(...result);
|
|
844
664
|
}
|
|
845
|
-
|
|
665
|
+
catch (error) {
|
|
666
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
667
|
+
returnData.push({
|
|
668
|
+
json: { error: errorMessage },
|
|
669
|
+
pairedItem: { item: i },
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
const result = await processItem(this, i, provider, aiProvider, credentials.apiKey);
|
|
675
|
+
returnData.push(...result);
|
|
676
|
+
}
|
|
846
677
|
}
|
|
847
|
-
|
|
848
|
-
}
|
|
678
|
+
return [returnData];
|
|
679
|
+
}
|
|
849
680
|
}
|
|
681
|
+
exports.UniversalAI = UniversalAI;
|
|
850
682
|
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);
|
|
683
|
+
const operation = exec.getNodeParameter('operation', index);
|
|
684
|
+
const model = exec.getNodeParameter('model', index);
|
|
685
|
+
const options = exec.getNodeParameter('options', index, {});
|
|
686
|
+
const input = await buildInput(exec, index);
|
|
687
|
+
const modelSettings = getModelSettings(exec, index, provider, operation, options);
|
|
688
|
+
return operation === 'generateText'
|
|
689
|
+
? await generateTextOperation(exec, index, provider, aiProvider, model, modelSettings, input, options, apiKey)
|
|
690
|
+
: await generateObjectOperation(exec, index, provider, aiProvider, model, modelSettings, input, options, apiKey);
|
|
861
691
|
}
|
|
862
692
|
function getModelSettings(exec, index, provider, operation, options) {
|
|
863
693
|
const settings = {};
|
|
@@ -868,14 +698,12 @@ function getModelSettings(exec, index, provider, operation, options) {
|
|
|
868
698
|
if (provider === 'google') {
|
|
869
699
|
const safetySettingsRaw = exec.getNodeParameter('safetySettings.settings', index, []);
|
|
870
700
|
if (safetySettingsRaw.length > 0) {
|
|
871
|
-
settings.safetySettings = safetySettingsRaw.map(s => ({
|
|
701
|
+
settings.safetySettings = safetySettingsRaw.map((s) => ({
|
|
872
702
|
category: s.category,
|
|
873
703
|
threshold: s.threshold,
|
|
874
704
|
}));
|
|
875
705
|
}
|
|
876
|
-
|
|
877
|
-
settings.structuredOutputs = true;
|
|
878
|
-
}
|
|
706
|
+
settings.structuredOutputs = operation === 'generateObject';
|
|
879
707
|
const responseModalities = exec.getNodeParameter('responseModalities', index, []);
|
|
880
708
|
if (responseModalities.length > 0) {
|
|
881
709
|
settings.responseModalities = responseModalities;
|
|
@@ -890,7 +718,7 @@ function buildGoogleProviderOptions(exec, index, cachedContentName) {
|
|
|
890
718
|
if (!Number.isNaN(thinkingBudgetValue) && thinkingBudgetValue > -1) {
|
|
891
719
|
options.thinkingConfig = {
|
|
892
720
|
thinkingBudget: Math.max(0, thinkingBudgetValue),
|
|
893
|
-
includeThoughts
|
|
721
|
+
includeThoughts,
|
|
894
722
|
};
|
|
895
723
|
}
|
|
896
724
|
if (cachedContentName) {
|
|
@@ -899,16 +727,20 @@ function buildGoogleProviderOptions(exec, index, cachedContentName) {
|
|
|
899
727
|
return Object.keys(options).length > 0 ? options : undefined;
|
|
900
728
|
}
|
|
901
729
|
function getGoogleCustomHeaders(exec, index) {
|
|
902
|
-
var _a, _b
|
|
730
|
+
var _a, _b;
|
|
903
731
|
const headersCollection = exec.getNodeParameter('customHeaders', index, {});
|
|
904
732
|
const entries = (_a = headersCollection === null || headersCollection === void 0 ? void 0 : headersCollection.headers) !== null && _a !== void 0 ? _a : [];
|
|
905
|
-
if (!entries.length)
|
|
733
|
+
if (!entries || entries.length === 0) {
|
|
906
734
|
return undefined;
|
|
735
|
+
}
|
|
907
736
|
const headers = {};
|
|
908
737
|
for (const entry of entries) {
|
|
909
|
-
if (
|
|
910
|
-
|
|
911
|
-
|
|
738
|
+
if (!entry)
|
|
739
|
+
continue;
|
|
740
|
+
const name = (entry.name || '').trim();
|
|
741
|
+
if (!name)
|
|
742
|
+
continue;
|
|
743
|
+
headers[name] = (_b = entry.value) !== null && _b !== void 0 ? _b : '';
|
|
912
744
|
}
|
|
913
745
|
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
914
746
|
}
|
|
@@ -921,101 +753,59 @@ async function prepareGoogleCache(exec, index, apiKey, input, tools, context) {
|
|
|
921
753
|
cachedContentName = await createGoogleCache(exec, index, apiKey, cacheContentInfo.content, tools);
|
|
922
754
|
}
|
|
923
755
|
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
|
-
}
|
|
756
|
+
console.warn(`UniversalAI: Cache creation for ${context} generation failed, continuing without cache:`, error);
|
|
930
757
|
}
|
|
931
758
|
}
|
|
932
759
|
const googleProviderOptions = buildGoogleProviderOptions(exec, index, cachedContentName || undefined);
|
|
933
760
|
return {
|
|
934
761
|
cachedContentName,
|
|
935
762
|
cacheContentInfo,
|
|
936
|
-
googleProviderOptions
|
|
763
|
+
googleProviderOptions,
|
|
937
764
|
};
|
|
938
765
|
}
|
|
939
766
|
async function generateTextOperation(exec, index, provider, aiProvider, model, modelSettings, input, options, apiKey) {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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);
|
|
972
|
-
}
|
|
973
|
-
const result = await (0, ai_1.generateText)(params);
|
|
974
|
-
const formattedResult = formatTextResult(result, includeRequestBody, provider);
|
|
975
|
-
return [{ json: formattedResult }];
|
|
976
|
-
}, 'generateTextOperation', exec, index);
|
|
977
|
-
}
|
|
978
|
-
async function generateObjectOperation(exec, index, provider, aiProvider, model, modelSettings, input, options, apiKey) {
|
|
979
|
-
return withErrorHandling(async () => {
|
|
980
|
-
const schemaName = exec.getNodeParameter('schemaName', index, '').trim();
|
|
981
|
-
const schemaDescription = exec.getNodeParameter('schemaDescription', index, '').trim();
|
|
982
|
-
const rawSchema = exec.getNodeParameter('schema', index);
|
|
983
|
-
const parsedSchema = parseAndValidateSchema(rawSchema, exec);
|
|
984
|
-
let cachedContentName = null;
|
|
985
|
-
let googleProviderOptions;
|
|
986
|
-
let cacheContentInfo;
|
|
987
|
-
if (provider === 'google') {
|
|
988
|
-
const cacheSetup = await prepareGoogleCache(exec, index, apiKey, input, undefined, 'object');
|
|
989
|
-
cachedContentName = cacheSetup.cachedContentName;
|
|
990
|
-
cacheContentInfo = cacheSetup.cacheContentInfo;
|
|
991
|
-
googleProviderOptions = cacheSetup.googleProviderOptions;
|
|
992
|
-
}
|
|
993
|
-
const finalContext = resolveFinalContext(input, cachedContentName, cacheContentInfo);
|
|
994
|
-
const params = {
|
|
995
|
-
model: aiProvider(model, modelSettings),
|
|
996
|
-
schema: (0, ai_1.jsonSchema)(parsedSchema),
|
|
997
|
-
schemaName,
|
|
998
|
-
schemaDescription,
|
|
999
|
-
...finalContext,
|
|
767
|
+
const enableStreaming = exec.getNodeParameter('enableStreaming', index, false);
|
|
768
|
+
const includeRequestBody = options.includeRequestBody;
|
|
769
|
+
const tools = provider === 'google' ? await buildGoogleTools(exec, index) : undefined;
|
|
770
|
+
let googleProviderOptions;
|
|
771
|
+
if (provider === 'google') {
|
|
772
|
+
const cacheSetup = await prepareGoogleCache(exec, index, apiKey, input, tools, 'text');
|
|
773
|
+
googleProviderOptions = cacheSetup.googleProviderOptions;
|
|
774
|
+
}
|
|
775
|
+
const params = {
|
|
776
|
+
model: aiProvider(model, modelSettings),
|
|
777
|
+
...input,
|
|
778
|
+
};
|
|
779
|
+
if (tools && Object.keys(tools).length > 0) {
|
|
780
|
+
params.tools = tools;
|
|
781
|
+
}
|
|
782
|
+
if (provider === 'google' && googleProviderOptions) {
|
|
783
|
+
params.providerOptions = {
|
|
784
|
+
google: googleProviderOptions,
|
|
1000
785
|
};
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
786
|
+
}
|
|
787
|
+
const textNumericKeys = [
|
|
788
|
+
'maxTokens',
|
|
789
|
+
'temperature',
|
|
790
|
+
'topP',
|
|
791
|
+
'topK',
|
|
792
|
+
'frequencyPenalty',
|
|
793
|
+
'presencePenalty',
|
|
794
|
+
'seed',
|
|
795
|
+
];
|
|
796
|
+
applyNumericOptions(params, options, textNumericKeys);
|
|
797
|
+
if (enableStreaming) {
|
|
798
|
+
return await handleStreaming(params, provider, includeRequestBody);
|
|
799
|
+
}
|
|
800
|
+
const result = await (0, ai_1.generateText)(params);
|
|
801
|
+
const formattedResult = formatTextResult(result, includeRequestBody, provider);
|
|
802
|
+
return [{ json: formattedResult }];
|
|
1014
803
|
}
|
|
1015
804
|
async function buildGoogleTools(exec, index) {
|
|
1016
805
|
const googleTools = exec.getNodeParameter('googleTools', index, []);
|
|
1017
|
-
if (!
|
|
806
|
+
if (!googleTools || googleTools.length === 0) {
|
|
1018
807
|
return undefined;
|
|
808
|
+
}
|
|
1019
809
|
const tools = {};
|
|
1020
810
|
const { google } = await Promise.resolve().then(() => __importStar(require('@ai-sdk/google')));
|
|
1021
811
|
const toolSet = new Set(googleTools);
|
|
@@ -1031,129 +821,77 @@ async function buildGoogleTools(exec, index) {
|
|
|
1031
821
|
return tools;
|
|
1032
822
|
}
|
|
1033
823
|
async function handleStreaming(params, provider, includeRequestBody) {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
824
|
+
const stream = await (0, ai_1.streamText)(params);
|
|
825
|
+
const chunks = [];
|
|
826
|
+
let fullText = '';
|
|
827
|
+
for await (const textPart of stream.textStream) {
|
|
828
|
+
fullText += textPart;
|
|
829
|
+
chunks.push({ json: { chunk: textPart, isStreaming: true } });
|
|
830
|
+
}
|
|
831
|
+
let finalUsage;
|
|
832
|
+
try {
|
|
833
|
+
finalUsage = await stream.usage;
|
|
834
|
+
}
|
|
835
|
+
catch (error) {
|
|
836
|
+
console.warn('UniversalAI: Failed to get usage from stream:', error);
|
|
837
|
+
finalUsage = undefined;
|
|
838
|
+
}
|
|
839
|
+
const finalJson = {
|
|
840
|
+
text: fullText,
|
|
841
|
+
toolCalls: stream.toolCalls || [],
|
|
842
|
+
toolResults: stream.toolResults || [],
|
|
843
|
+
finishReason: stream.finishReason,
|
|
844
|
+
usage: finalUsage ? formatUsage({ usage: finalUsage }, provider) : undefined,
|
|
845
|
+
isStreaming: false,
|
|
846
|
+
isFinal: true,
|
|
847
|
+
};
|
|
848
|
+
if (includeRequestBody) {
|
|
1038
849
|
try {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
json: {
|
|
1043
|
-
chunk: textPart,
|
|
1044
|
-
isStreaming: true
|
|
1045
|
-
}
|
|
1046
|
-
});
|
|
850
|
+
const requestMetadata = stream.request ? await stream.request : undefined;
|
|
851
|
+
if ((requestMetadata === null || requestMetadata === void 0 ? void 0 : requestMetadata.body) !== undefined) {
|
|
852
|
+
finalJson.request = { body: requestMetadata.body };
|
|
1047
853
|
}
|
|
1048
854
|
}
|
|
1049
855
|
catch (error) {
|
|
1050
|
-
|
|
1051
|
-
}
|
|
1052
|
-
let finalUsage;
|
|
1053
|
-
let requestMetadata;
|
|
1054
|
-
try {
|
|
1055
|
-
finalUsage = await stream.usage;
|
|
856
|
+
console.warn('UniversalAI: Failed to get request metadata from stream:', error);
|
|
1056
857
|
}
|
|
1057
|
-
catch (error) {
|
|
1058
|
-
console.warn('Could not get usage from stream:', error);
|
|
1059
|
-
}
|
|
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
858
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
859
|
+
chunks.push({ json: finalJson });
|
|
860
|
+
return chunks;
|
|
861
|
+
}
|
|
862
|
+
async function generateObjectOperation(exec, index, provider, aiProvider, model, modelSettings, input, options, apiKey) {
|
|
863
|
+
const schemaName = exec.getNodeParameter('schemaName', index, '');
|
|
864
|
+
const schemaDescription = exec.getNodeParameter('schemaDescription', index, '');
|
|
865
|
+
const rawSchema = exec.getNodeParameter('schema', index);
|
|
866
|
+
const parsedSchema = parseAndValidateSchema(rawSchema, exec);
|
|
867
|
+
let googleProviderOptions;
|
|
868
|
+
if (provider === 'google') {
|
|
869
|
+
const cacheSetup = await prepareGoogleCache(exec, index, apiKey, input, undefined, 'object');
|
|
870
|
+
googleProviderOptions = cacheSetup.googleProviderOptions;
|
|
871
|
+
}
|
|
872
|
+
const params = {
|
|
873
|
+
model: aiProvider(model, modelSettings),
|
|
874
|
+
schema: (0, ai_1.jsonSchema)(parsedSchema),
|
|
875
|
+
schemaName,
|
|
876
|
+
schemaDescription,
|
|
877
|
+
...input,
|
|
878
|
+
};
|
|
879
|
+
if (provider === 'google' && googleProviderOptions) {
|
|
880
|
+
params.providerOptions = {
|
|
881
|
+
google: googleProviderOptions,
|
|
1129
882
|
};
|
|
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
883
|
}
|
|
884
|
+
const objectNumericKeys = [
|
|
885
|
+
'temperature',
|
|
886
|
+
'topP',
|
|
887
|
+
'topK',
|
|
888
|
+
'frequencyPenalty',
|
|
889
|
+
'presencePenalty',
|
|
890
|
+
'seed',
|
|
891
|
+
];
|
|
892
|
+
applyNumericOptions(params, options, objectNumericKeys);
|
|
893
|
+
const result = await (0, ai_1.generateObject)(params);
|
|
894
|
+
const formattedResult = formatObjectResult(result, options.includeRequestBody, provider);
|
|
895
|
+
return [{ json: formattedResult }];
|
|
1157
896
|
}
|
|
1158
|
-
exports.UniversalAI = UniversalAI;
|
|
1159
897
|
//# sourceMappingURL=UniversalAI.node.js.map
|