@aj-archipelago/cortex 1.4.6 → 1.4.7
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.
- package/helper-apps/cortex-file-handler/package-lock.json +2 -2
- package/helper-apps/cortex-file-handler/package.json +1 -1
- package/helper-apps/cortex-file-handler/src/index.js +27 -4
- package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +74 -10
- package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +23 -2
- package/helper-apps/cortex-file-handler/src/start.js +2 -0
- package/helper-apps/cortex-file-handler/tests/deleteOperations.test.js +287 -0
- package/helper-apps/cortex-file-handler/tests/start.test.js +1 -1
- package/lib/entityConstants.js +1 -1
- package/lib/fileUtils.js +1481 -0
- package/lib/pathwayTools.js +7 -1
- package/lib/util.js +2 -313
- package/package.json +4 -3
- package/pathways/image_qwen.js +1 -1
- package/pathways/system/entity/memory/sys_read_memory.js +17 -3
- package/pathways/system/entity/memory/sys_save_memory.js +22 -6
- package/pathways/system/entity/sys_entity_agent.js +21 -4
- package/pathways/system/entity/tools/sys_tool_analyzefile.js +171 -0
- package/pathways/system/entity/tools/sys_tool_codingagent.js +38 -4
- package/pathways/system/entity/tools/sys_tool_editfile.js +403 -0
- package/pathways/system/entity/tools/sys_tool_file_collection.js +433 -0
- package/pathways/system/entity/tools/sys_tool_image.js +172 -10
- package/pathways/system/entity/tools/sys_tool_image_gemini.js +123 -10
- package/pathways/system/entity/tools/sys_tool_readfile.js +217 -124
- package/pathways/system/entity/tools/sys_tool_validate_url.js +137 -0
- package/pathways/system/entity/tools/sys_tool_writefile.js +211 -0
- package/pathways/system/workspaces/run_workspace_prompt.js +4 -3
- package/pathways/transcribe_gemini.js +2 -1
- package/server/executeWorkspace.js +1 -1
- package/server/plugins/neuralSpacePlugin.js +2 -6
- package/server/plugins/openAiWhisperPlugin.js +2 -1
- package/server/plugins/replicateApiPlugin.js +4 -14
- package/server/typeDef.js +10 -1
- package/tests/integration/features/tools/fileCollection.test.js +858 -0
- package/tests/integration/features/tools/fileOperations.test.js +851 -0
- package/tests/integration/features/tools/writefile.test.js +350 -0
- package/tests/unit/core/fileCollection.test.js +259 -0
- package/tests/unit/core/util.test.js +320 -1
package/lib/pathwayTools.js
CHANGED
|
@@ -63,7 +63,13 @@ const callTool = async (toolName, args, toolDefinitions, pathwayResolver) => {
|
|
|
63
63
|
throw new Error(`Tool ${toolName} not found in available tools`);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
// Create a sanitized copy of args for logging (exclude large objects like chatHistory)
|
|
67
|
+
const logArgs = { ...args };
|
|
68
|
+
if (logArgs.chatHistory) {
|
|
69
|
+
logArgs.chatHistory = `[${Array.isArray(logArgs.chatHistory) ? logArgs.chatHistory.length : 'N/A'} messages]`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
logger.debug(`callTool: Starting execution of ${toolName} ${JSON.stringify(logArgs)}`);
|
|
67
73
|
|
|
68
74
|
try {
|
|
69
75
|
const pathwayName = toolDef.pathwayName;
|
package/lib/util.js
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
import logger from "./logger.js";
|
|
2
|
-
import stream from 'stream';
|
|
3
2
|
import subvibe from '@aj-archipelago/subvibe';
|
|
4
|
-
import os from 'os';
|
|
5
|
-
import http from 'http';
|
|
6
|
-
import https from 'https';
|
|
7
3
|
import { URL } from 'url';
|
|
8
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
9
|
-
import { promisify } from 'util';
|
|
10
|
-
import { axios } from './requestExecutor.js';
|
|
11
|
-
import { config } from '../config.js';
|
|
12
|
-
import fs from 'fs';
|
|
13
|
-
import path from 'path';
|
|
14
|
-
import FormData from 'form-data';
|
|
15
5
|
|
|
16
|
-
const pipeline = promisify(stream.pipeline);
|
|
17
|
-
const MEDIA_API_URL = config.get('whisperMediaApiUrl');
|
|
18
6
|
|
|
19
7
|
function getUniqueId(){
|
|
20
8
|
return uuidv4();
|
|
@@ -107,70 +95,6 @@ function chatArgsHasImageUrl(args){
|
|
|
107
95
|
return chatArgsHasType(args, 'image_url');
|
|
108
96
|
}
|
|
109
97
|
|
|
110
|
-
|
|
111
|
-
async function deleteTempPath(path) {
|
|
112
|
-
try {
|
|
113
|
-
if (!path) {
|
|
114
|
-
logger.warn('Temporary path is not defined.');
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (!fs.existsSync(path)) {
|
|
118
|
-
logger.warn(`Temporary path ${path} does not exist.`);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const stats = fs.statSync(path);
|
|
122
|
-
if (stats.isFile()) {
|
|
123
|
-
fs.unlinkSync(path);
|
|
124
|
-
logger.info(`Temporary file ${path} deleted successfully.`);
|
|
125
|
-
} else if (stats.isDirectory()) {
|
|
126
|
-
fs.rmSync(path, { recursive: true });
|
|
127
|
-
logger.info(`Temporary folder ${path} and its contents deleted successfully.`);
|
|
128
|
-
}
|
|
129
|
-
} catch (err) {
|
|
130
|
-
logger.error(`Error occurred while deleting the temporary path: ${err}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function generateUniqueFilename(extension) {
|
|
135
|
-
return `${uuidv4()}.${extension}`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const downloadFile = async (fileUrl) => {
|
|
139
|
-
const urlObj = new URL(fileUrl);
|
|
140
|
-
const pathname = urlObj.pathname;
|
|
141
|
-
const fileExtension = pathname.substring(pathname.lastIndexOf('.') + 1);
|
|
142
|
-
const uniqueFilename = generateUniqueFilename(fileExtension);
|
|
143
|
-
const tempDir = os.tmpdir();
|
|
144
|
-
const localFilePath = `${tempDir}/${uniqueFilename}`;
|
|
145
|
-
|
|
146
|
-
// eslint-disable-next-line no-async-promise-executor
|
|
147
|
-
return new Promise(async (resolve, reject) => {
|
|
148
|
-
try {
|
|
149
|
-
const parsedUrl = new URL(fileUrl);
|
|
150
|
-
const protocol = parsedUrl.protocol === 'https:' ? https : http;
|
|
151
|
-
|
|
152
|
-
const response = await new Promise((resolve, reject) => {
|
|
153
|
-
protocol.get(parsedUrl, (res) => {
|
|
154
|
-
if (res.statusCode === 200) {
|
|
155
|
-
resolve(res);
|
|
156
|
-
} else {
|
|
157
|
-
reject(new Error(`HTTP request failed with status code ${res.statusCode}`));
|
|
158
|
-
}
|
|
159
|
-
}).on('error', reject);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
await pipeline(response, fs.createWriteStream(localFilePath));
|
|
163
|
-
logger.info(`Downloaded file to ${localFilePath}`);
|
|
164
|
-
resolve(localFilePath);
|
|
165
|
-
} catch (error) {
|
|
166
|
-
fs.unlink(localFilePath, () => {
|
|
167
|
-
reject(error);
|
|
168
|
-
});
|
|
169
|
-
//throw error;
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
};
|
|
173
|
-
|
|
174
98
|
// convert srt format to text
|
|
175
99
|
function convertSrtToText(str) {
|
|
176
100
|
return str
|
|
@@ -223,36 +147,6 @@ function alignSubtitles(subtitles, format, offsets) {
|
|
|
223
147
|
}
|
|
224
148
|
}
|
|
225
149
|
|
|
226
|
-
|
|
227
|
-
async function getMediaChunks(file, requestId) {
|
|
228
|
-
try {
|
|
229
|
-
if (MEDIA_API_URL) {
|
|
230
|
-
//call helper api and get list of file uris
|
|
231
|
-
const res = await axios.get(MEDIA_API_URL, { params: { uri: file, requestId } });
|
|
232
|
-
return res.data;
|
|
233
|
-
} else {
|
|
234
|
-
logger.info(`No API_URL set, returning file as chunk`);
|
|
235
|
-
return [file];
|
|
236
|
-
}
|
|
237
|
-
} catch (err) {
|
|
238
|
-
logger.error(`Error getting media chunks list from api: ${err}`);
|
|
239
|
-
throw err;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async function markCompletedForCleanUp(requestId) {
|
|
244
|
-
try {
|
|
245
|
-
if (MEDIA_API_URL) {
|
|
246
|
-
//call helper api to mark processing as completed
|
|
247
|
-
const res = await axios.delete(MEDIA_API_URL, { params: { requestId } });
|
|
248
|
-
logger.info(`Marked request ${requestId} as completed: ${JSON.stringify(res.data)}`);
|
|
249
|
-
return res.data;
|
|
250
|
-
}
|
|
251
|
-
} catch (err) {
|
|
252
|
-
logger.error(`Error marking request ${requestId} as completed: ${err}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
150
|
function removeOldImageAndFileContent(chatHistory) {
|
|
257
151
|
if (!chatHistory || !Array.isArray(chatHistory) || chatHistory.length === 0) {
|
|
258
152
|
return chatHistory;
|
|
@@ -387,204 +281,6 @@ function removeImageAndFileFromMessage(message) {
|
|
|
387
281
|
return modifiedMessage;
|
|
388
282
|
}
|
|
389
283
|
|
|
390
|
-
// Helper function to extract file URLs from a content object
|
|
391
|
-
function extractFileUrlsFromContent(contentObj) {
|
|
392
|
-
const urls = [];
|
|
393
|
-
if (contentObj.type === 'image_url' && contentObj.image_url?.url) {
|
|
394
|
-
urls.push(contentObj.image_url.url);
|
|
395
|
-
} else if (contentObj.type === 'file' && contentObj.file) {
|
|
396
|
-
urls.push(contentObj.file);
|
|
397
|
-
}
|
|
398
|
-
return urls;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function getAvailableFiles(chatHistory) {
|
|
402
|
-
const availableFiles = [];
|
|
403
|
-
|
|
404
|
-
if (!chatHistory || !Array.isArray(chatHistory)) {
|
|
405
|
-
return availableFiles;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
for (const message of chatHistory) {
|
|
409
|
-
if (!message || !message.content) {
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Handle array content
|
|
414
|
-
if (Array.isArray(message.content)) {
|
|
415
|
-
for (const content of message.content) {
|
|
416
|
-
try {
|
|
417
|
-
const contentObj = typeof content === 'string' ? JSON.parse(content) : content;
|
|
418
|
-
availableFiles.push(...extractFileUrlsFromContent(contentObj));
|
|
419
|
-
} catch (e) {
|
|
420
|
-
// Not JSON or couldn't be parsed, continue
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
// Handle string content
|
|
426
|
-
else if (typeof message.content === 'string') {
|
|
427
|
-
try {
|
|
428
|
-
const contentObj = JSON.parse(message.content);
|
|
429
|
-
availableFiles.push(...extractFileUrlsFromContent(contentObj));
|
|
430
|
-
} catch (e) {
|
|
431
|
-
// Not JSON or couldn't be parsed, continue
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
// Handle object content
|
|
436
|
-
else if (typeof message.content === 'object') {
|
|
437
|
-
availableFiles.push(...extractFileUrlsFromContent(message.content));
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
return availableFiles;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// Helper function to upload base64 image data to cloud storage
|
|
445
|
-
const uploadImageToCloud = async (base64Data, mimeType, pathwayResolver = null) => {
|
|
446
|
-
let tempFilePath = null;
|
|
447
|
-
let tempDir = null;
|
|
448
|
-
|
|
449
|
-
try {
|
|
450
|
-
// Convert base64 to buffer
|
|
451
|
-
const imageBuffer = Buffer.from(base64Data, 'base64');
|
|
452
|
-
|
|
453
|
-
// Determine file extension from mime type
|
|
454
|
-
const extension = mimeType.split('/')[1] || 'png';
|
|
455
|
-
const filename = `generated_image_${Date.now()}.${extension}`;
|
|
456
|
-
|
|
457
|
-
// Create temporary file
|
|
458
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'image-upload-'));
|
|
459
|
-
tempFilePath = path.join(tempDir, filename);
|
|
460
|
-
|
|
461
|
-
// Write buffer to temp file
|
|
462
|
-
fs.writeFileSync(tempFilePath, imageBuffer);
|
|
463
|
-
|
|
464
|
-
// Upload to file handler service
|
|
465
|
-
const fileHandlerUrl = MEDIA_API_URL;
|
|
466
|
-
if (!fileHandlerUrl) {
|
|
467
|
-
throw new Error('WHISPER_MEDIA_API_URL is not set');
|
|
468
|
-
}
|
|
469
|
-
const requestId = uuidv4();
|
|
470
|
-
|
|
471
|
-
// Create form data for upload
|
|
472
|
-
const formData = new FormData();
|
|
473
|
-
formData.append('file', fs.createReadStream(tempFilePath), {
|
|
474
|
-
filename: filename,
|
|
475
|
-
contentType: mimeType
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
// Append requestId parameter (preserving existing query parameters like subscription-key)
|
|
479
|
-
const separator = fileHandlerUrl.includes('?') ? '&' : '?';
|
|
480
|
-
const uploadUrl = `${fileHandlerUrl}${separator}requestId=${requestId}`;
|
|
481
|
-
|
|
482
|
-
// Upload file
|
|
483
|
-
const uploadResponse = await axios.post(uploadUrl, formData, {
|
|
484
|
-
headers: {
|
|
485
|
-
...formData.getHeaders()
|
|
486
|
-
},
|
|
487
|
-
timeout: 30000
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
if (uploadResponse.data && uploadResponse.data.url) {
|
|
491
|
-
return uploadResponse.data.url;
|
|
492
|
-
} else {
|
|
493
|
-
throw new Error('No URL returned from file handler');
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
} catch (error) {
|
|
497
|
-
const errorMessage = `Failed to upload image: ${error.message}`;
|
|
498
|
-
if (pathwayResolver && pathwayResolver.logError) {
|
|
499
|
-
pathwayResolver.logError(errorMessage);
|
|
500
|
-
} else {
|
|
501
|
-
logger.error(errorMessage);
|
|
502
|
-
}
|
|
503
|
-
throw error;
|
|
504
|
-
} finally {
|
|
505
|
-
// Clean up temp files
|
|
506
|
-
if (tempFilePath && fs.existsSync(tempFilePath)) {
|
|
507
|
-
try {
|
|
508
|
-
fs.unlinkSync(tempFilePath);
|
|
509
|
-
} catch (cleanupError) {
|
|
510
|
-
const warningMessage = `Failed to clean up temp file: ${cleanupError.message}`;
|
|
511
|
-
if (pathwayResolver && pathwayResolver.logWarning) {
|
|
512
|
-
pathwayResolver.logWarning(warningMessage);
|
|
513
|
-
} else {
|
|
514
|
-
logger.warn(warningMessage);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
if (tempDir && fs.existsSync(tempDir)) {
|
|
519
|
-
try {
|
|
520
|
-
fs.rmdirSync(tempDir);
|
|
521
|
-
} catch (cleanupError) {
|
|
522
|
-
const warningMessage = `Failed to clean up temp directory: ${cleanupError.message}`;
|
|
523
|
-
if (pathwayResolver && pathwayResolver.logWarning) {
|
|
524
|
-
pathwayResolver.logWarning(warningMessage);
|
|
525
|
-
} else {
|
|
526
|
-
logger.warn(warningMessage);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Convert file hashes to content format suitable for LLM processing
|
|
535
|
-
* @param {Array<string>} fileHashes - Array of file hashes to resolve
|
|
536
|
-
* @param {Object} config - Configuration object with file service endpoints
|
|
537
|
-
* @returns {Promise<Array<string>>} Array of stringified file content objects
|
|
538
|
-
*/
|
|
539
|
-
async function resolveFileHashesToContent(fileHashes, config) {
|
|
540
|
-
if (!fileHashes || fileHashes.length === 0) return [];
|
|
541
|
-
|
|
542
|
-
const fileContentPromises = fileHashes.map(async (hash) => {
|
|
543
|
-
try {
|
|
544
|
-
// Use the existing file handler (cortex-file-handler) to resolve file hashes
|
|
545
|
-
const fileHandlerUrl = config?.get?.('whisperMediaApiUrl');
|
|
546
|
-
|
|
547
|
-
if (fileHandlerUrl && fileHandlerUrl !== 'null') {
|
|
548
|
-
// Make request to file handler to get file content by hash
|
|
549
|
-
const response = await axios.get(fileHandlerUrl, {
|
|
550
|
-
params: { hash: hash, checkHash: true }
|
|
551
|
-
});
|
|
552
|
-
if (response.status === 200) {
|
|
553
|
-
const fileData = response.data;
|
|
554
|
-
const fileUrl = fileData.shortLivedUrl || fileData.url;
|
|
555
|
-
const convertedUrl = fileData.converted?.url;
|
|
556
|
-
const convertedGcsUrl = fileData.converted?.gcs;
|
|
557
|
-
|
|
558
|
-
return JSON.stringify({
|
|
559
|
-
type: "image_url",
|
|
560
|
-
url: convertedUrl,
|
|
561
|
-
image_url: { url: convertedUrl },
|
|
562
|
-
gcs: convertedGcsUrl || fileData.gcs, // Add GCS URL for Gemini models
|
|
563
|
-
originalFilename: fileData.filename,
|
|
564
|
-
hash: hash
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// Fallback: create a placeholder that indicates file resolution is needed
|
|
570
|
-
return JSON.stringify({
|
|
571
|
-
type: "file_hash",
|
|
572
|
-
hash: hash,
|
|
573
|
-
_cortex_needs_resolution: true
|
|
574
|
-
});
|
|
575
|
-
} catch (error) {
|
|
576
|
-
// Return error indicator
|
|
577
|
-
return JSON.stringify({
|
|
578
|
-
type: "file_error",
|
|
579
|
-
hash: hash,
|
|
580
|
-
error: error.message
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
return Promise.all(fileContentPromises);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
284
|
export {
|
|
589
285
|
getUniqueId,
|
|
590
286
|
getSearchResultId,
|
|
@@ -592,14 +288,7 @@ export {
|
|
|
592
288
|
convertToSingleContentChatHistory,
|
|
593
289
|
chatArgsHasImageUrl,
|
|
594
290
|
chatArgsHasType,
|
|
595
|
-
deleteTempPath,
|
|
596
|
-
downloadFile,
|
|
597
|
-
uploadImageToCloud,
|
|
598
291
|
convertSrtToText,
|
|
599
292
|
alignSubtitles,
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
removeOldImageAndFileContent,
|
|
603
|
-
getAvailableFiles,
|
|
604
|
-
resolveFileHashesToContent
|
|
605
|
-
};
|
|
293
|
+
removeOldImageAndFileContent
|
|
294
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aj-archipelago/cortex",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
4
4
|
"description": "Cortex is a GraphQL API for AI. It provides a simple, extensible interface for using AI services from OpenAI, Azure and others.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"type": "module",
|
|
34
34
|
"homepage": "https://github.com/aj-archipelago/cortex#readme",
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@aj-archipelago/merval": "^1.0.
|
|
36
|
+
"@aj-archipelago/merval": "^1.0.4",
|
|
37
37
|
"@aj-archipelago/subvibe": "^1.0.12",
|
|
38
38
|
"@apollo/server": "^4.7.3",
|
|
39
39
|
"@apollo/server-plugin-response-cache": "^4.1.2",
|
|
@@ -67,7 +67,8 @@
|
|
|
67
67
|
"mime-types": "^2.1.35",
|
|
68
68
|
"uuid": "^9.0.0",
|
|
69
69
|
"winston": "^3.11.0",
|
|
70
|
-
"ws": "^8.12.0"
|
|
70
|
+
"ws": "^8.12.0",
|
|
71
|
+
"xxhash-wasm": "^1.1.0"
|
|
71
72
|
},
|
|
72
73
|
"devDependencies": {
|
|
73
74
|
"@faker-js/faker": "^8.4.1",
|
package/pathways/image_qwen.js
CHANGED
|
@@ -7,7 +7,7 @@ export default {
|
|
|
7
7
|
negativePrompt: "",
|
|
8
8
|
width: 1024,
|
|
9
9
|
height: 1024,
|
|
10
|
-
aspectRatio: "
|
|
10
|
+
aspectRatio: "match_input_image", // Options: "1:1", "16:9", "9:16", "4:3", "3:4", "match_input_image" (use "match_input_image" for qwen-image-edit-plus)
|
|
11
11
|
numberResults: 1,
|
|
12
12
|
output_format: "webp",
|
|
13
13
|
output_quality: 80, // Use 95 for qwen-image-edit-plus
|
|
@@ -99,19 +99,33 @@ export default {
|
|
|
99
99
|
return savedContext.memoryContext || "";
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
const validSections = ['memorySelf', 'memoryDirectives', 'memoryTopics', 'memoryUser', 'memoryContext', 'memoryVersion'];
|
|
102
|
+
const validSections = ['memorySelf', 'memoryDirectives', 'memoryTopics', 'memoryUser', 'memoryContext', 'memoryVersion', 'memoryFiles'];
|
|
103
|
+
// memoryFiles can only be accessed explicitly, not as part of memoryAll
|
|
104
|
+
const allSections = ['memorySelf', 'memoryDirectives', 'memoryTopics', 'memoryUser', 'memoryContext', 'memoryVersion'];
|
|
103
105
|
|
|
104
106
|
if (section !== 'memoryAll') {
|
|
105
107
|
if (validSections.includes(section)) {
|
|
106
108
|
const content = (getvWithDoubleDecryption && (await getvWithDoubleDecryption(`${contextId}-${section}`, contextKey))) || "";
|
|
109
|
+
// memoryFiles is JSON, skip processing but ensure it's a string
|
|
110
|
+
if (section === 'memoryFiles') {
|
|
111
|
+
if (!content) {
|
|
112
|
+
return "[]";
|
|
113
|
+
}
|
|
114
|
+
// If content is already an object (from getvWithDoubleDecryption parsing), stringify it
|
|
115
|
+
if (typeof content === 'object') {
|
|
116
|
+
return JSON.stringify(content);
|
|
117
|
+
}
|
|
118
|
+
// Otherwise it's already a string, return as-is
|
|
119
|
+
return content;
|
|
120
|
+
}
|
|
107
121
|
return processMemoryContent(content, options);
|
|
108
122
|
}
|
|
109
123
|
return "";
|
|
110
124
|
}
|
|
111
125
|
|
|
112
|
-
// otherwise, read all sections and return them as a JSON object
|
|
126
|
+
// otherwise, read all sections (excluding memoryFiles) and return them as a JSON object
|
|
113
127
|
const memoryContents = {};
|
|
114
|
-
for (const section of
|
|
128
|
+
for (const section of allSections) {
|
|
115
129
|
if (section === 'memoryContext') continue;
|
|
116
130
|
|
|
117
131
|
const content = (getvWithDoubleDecryption && (await getvWithDoubleDecryption(`${contextId}-${section}`, contextKey))) || "";
|
|
@@ -29,34 +29,50 @@ export default {
|
|
|
29
29
|
return aiMemory;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const validSections = ['memorySelf', 'memoryDirectives', 'memoryTopics', 'memoryUser', 'memoryVersion'];
|
|
32
|
+
const validSections = ['memorySelf', 'memoryDirectives', 'memoryTopics', 'memoryUser', 'memoryVersion', 'memoryFiles'];
|
|
33
|
+
// memoryFiles can only be accessed explicitly, not as part of memoryAll
|
|
34
|
+
const allSections = ['memorySelf', 'memoryDirectives', 'memoryTopics', 'memoryUser', 'memoryVersion'];
|
|
33
35
|
|
|
34
36
|
// Handle single section save
|
|
35
37
|
if (section !== 'memoryAll') {
|
|
36
38
|
if (validSections.includes(section)) {
|
|
39
|
+
// memoryFiles should be JSON array, validate if provided
|
|
40
|
+
if (section === 'memoryFiles' && aiMemory && aiMemory.trim() !== '') {
|
|
41
|
+
try {
|
|
42
|
+
// Validate it's valid JSON (but keep as string for storage)
|
|
43
|
+
JSON.parse(aiMemory);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
// If not valid JSON, return error
|
|
46
|
+
return JSON.stringify({ error: 'memoryFiles must be a valid JSON array' });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
37
49
|
await setvWithDoubleEncryption(`${contextId}-${section}`, aiMemory, contextKey);
|
|
38
50
|
}
|
|
39
51
|
return aiMemory;
|
|
40
52
|
}
|
|
41
53
|
|
|
42
|
-
// if the aiMemory is an empty string, set all sections to empty strings
|
|
54
|
+
// if the aiMemory is an empty string, set all sections (excluding memoryFiles) to empty strings
|
|
43
55
|
if (aiMemory.trim() === "") {
|
|
44
|
-
for (const section of
|
|
56
|
+
for (const section of allSections) {
|
|
45
57
|
await setvWithDoubleEncryption(`${contextId}-${section}`, "", contextKey);
|
|
46
58
|
}
|
|
47
59
|
return "";
|
|
48
60
|
}
|
|
49
61
|
|
|
50
|
-
// Handle multi-section save
|
|
62
|
+
// Handle multi-section save (excluding memoryFiles)
|
|
51
63
|
try {
|
|
52
64
|
const memoryObject = JSON.parse(aiMemory);
|
|
53
|
-
for (const section of
|
|
65
|
+
for (const section of allSections) {
|
|
54
66
|
if (section in memoryObject) {
|
|
55
67
|
await setvWithDoubleEncryption(`${contextId}-${section}`, memoryObject[section], contextKey);
|
|
56
68
|
}
|
|
57
69
|
}
|
|
70
|
+
// Explicitly ignore memoryFiles if present in the object
|
|
71
|
+
if ('memoryFiles' in memoryObject) {
|
|
72
|
+
// Silently ignore - memoryFiles can only be saved explicitly
|
|
73
|
+
}
|
|
58
74
|
} catch {
|
|
59
|
-
for (const section of
|
|
75
|
+
for (const section of allSections) {
|
|
60
76
|
await setvWithDoubleEncryption(`${contextId}-${section}`, "", contextKey);
|
|
61
77
|
}
|
|
62
78
|
await setvWithDoubleEncryption(`${contextId}-memoryUser`, aiMemory, contextKey);
|
|
@@ -5,7 +5,8 @@ const MAX_TOOL_CALLS = 50;
|
|
|
5
5
|
import { callPathway, callTool, say } from '../../../lib/pathwayTools.js';
|
|
6
6
|
import logger from '../../../lib/logger.js';
|
|
7
7
|
import { config } from '../../../config.js';
|
|
8
|
-
import { chatArgsHasImageUrl, removeOldImageAndFileContent
|
|
8
|
+
import { chatArgsHasImageUrl, removeOldImageAndFileContent } from '../../../lib/util.js';
|
|
9
|
+
import { getAvailableFiles } from '../../../lib/fileUtils.js';
|
|
9
10
|
import { Prompt } from '../../../server/prompt.js';
|
|
10
11
|
import { getToolsForEntity, loadEntityConfig } from './tools/shared/sys_entity_tools.js';
|
|
11
12
|
import CortexResponse from '../../../lib/cortexResponse.js';
|
|
@@ -329,7 +330,11 @@ export default {
|
|
|
329
330
|
memoryLookupRequiredPromise = Promise.race([
|
|
330
331
|
callPathway('sys_memory_lookup_required', { ...args, chatHistory: chatHistoryLastTurn, stream: false }),
|
|
331
332
|
timeoutPromise
|
|
332
|
-
])
|
|
333
|
+
]).catch(error => {
|
|
334
|
+
// Handle timeout or other errors gracefully - return null so the await doesn't throw
|
|
335
|
+
logger.warn(`Memory lookup promise rejected: ${error.message}`);
|
|
336
|
+
return null;
|
|
337
|
+
});
|
|
333
338
|
}
|
|
334
339
|
}
|
|
335
340
|
|
|
@@ -388,7 +393,8 @@ export default {
|
|
|
388
393
|
args.chatHistory = args.chatHistory.slice(-20);
|
|
389
394
|
}
|
|
390
395
|
|
|
391
|
-
|
|
396
|
+
// Get available files from collection (async, syncs files from chat history)
|
|
397
|
+
const availableFiles = await getAvailableFiles(args.chatHistory, args.contextId, args.contextKey);
|
|
392
398
|
|
|
393
399
|
// remove old image and file content
|
|
394
400
|
const visionContentPresent = chatArgsHasImageUrl(args);
|
|
@@ -407,7 +413,18 @@ export default {
|
|
|
407
413
|
|
|
408
414
|
try {
|
|
409
415
|
if (memoryLookupRequiredPromise) {
|
|
410
|
-
|
|
416
|
+
const result = await memoryLookupRequiredPromise;
|
|
417
|
+
// If result is null (timeout) or empty, default to false
|
|
418
|
+
if (result && typeof result === 'string') {
|
|
419
|
+
try {
|
|
420
|
+
memoryLookupRequired = JSON.parse(result)?.memoryRequired || false;
|
|
421
|
+
} catch (parseError) {
|
|
422
|
+
logger.warn(`Failed to parse memory lookup result: ${parseError.message}`);
|
|
423
|
+
memoryLookupRequired = false;
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
memoryLookupRequired = false;
|
|
427
|
+
}
|
|
411
428
|
} else {
|
|
412
429
|
memoryLookupRequired = false;
|
|
413
430
|
}
|