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