@aj-archipelago/cortex 1.4.22 → 1.4.23
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/FILE_SYSTEM_DOCUMENTATION.md +116 -48
- package/config.js +9 -0
- package/lib/fileUtils.js +226 -201
- package/package.json +1 -1
- package/pathways/system/entity/files/sys_read_file_collection.js +13 -11
- package/pathways/system/entity/files/sys_update_file_metadata.js +16 -7
- package/pathways/system/entity/sys_entity_agent.js +8 -6
- package/pathways/system/entity/tools/sys_tool_codingagent.js +4 -4
- package/pathways/system/entity/tools/sys_tool_editfile.js +27 -22
- package/pathways/system/entity/tools/sys_tool_file_collection.js +15 -10
- package/pathways/system/entity/tools/sys_tool_image.js +1 -1
- package/pathways/system/entity/tools/sys_tool_image_gemini.js +1 -1
- package/pathways/system/entity/tools/sys_tool_readfile.js +4 -4
- package/pathways/system/entity/tools/sys_tool_slides_gemini.js +1 -1
- package/pathways/system/entity/tools/sys_tool_video_veo.js +1 -1
- package/pathways/system/entity/tools/sys_tool_view_image.js +10 -5
- package/pathways/system/workspaces/run_workspace_agent.js +4 -1
- package/pathways/video_seedance.js +2 -0
- package/server/executeWorkspace.js +45 -2
- package/server/pathwayResolver.js +18 -0
- package/server/plugins/replicateApiPlugin.js +18 -0
- package/server/typeDef.js +10 -1
- package/test.log +39427 -0
- package/tests/integration/features/tools/fileCollection.test.js +254 -248
- package/tests/integration/features/tools/fileOperations.test.js +131 -81
- package/tests/integration/graphql/async/stream/vendors/claude_streaming.test.js +3 -4
- package/tests/integration/graphql/async/stream/vendors/gemini_streaming.test.js +3 -4
- package/tests/integration/graphql/async/stream/vendors/grok_streaming.test.js +3 -4
- package/tests/integration/graphql/async/stream/vendors/openai_streaming.test.js +5 -5
- package/tests/unit/core/fileCollection.test.js +86 -25
- package/pathways/system/workspaces/run_workspace_research_agent.js +0 -27
|
@@ -2,31 +2,40 @@
|
|
|
2
2
|
// GraphQL pathway for updating file metadata (replaces sys_save_memory for renames and metadata updates)
|
|
3
3
|
// Only updates Cortex-managed fields (displayFilename, tags, notes, etc.), not CFH fields (url, gcs, hash, filename)
|
|
4
4
|
|
|
5
|
-
import { updateFileMetadata } from '../../../../lib/fileUtils.js';
|
|
5
|
+
import { updateFileMetadata, getDefaultContext } from '../../../../lib/fileUtils.js';
|
|
6
6
|
|
|
7
7
|
export default {
|
|
8
8
|
inputParameters: {
|
|
9
|
-
contextId: ``,
|
|
9
|
+
agentContext: [{ contextId: ``, contextKey: ``, default: true }],
|
|
10
10
|
hash: ``,
|
|
11
11
|
displayFilename: { type: 'string' }, // Optional - no default
|
|
12
12
|
tags: { type: 'array', items: { type: 'string' } }, // Optional - no default
|
|
13
13
|
notes: { type: 'string' }, // Optional - no default
|
|
14
14
|
mimeType: { type: 'string' }, // Optional - no default
|
|
15
15
|
permanent: { type: 'boolean' }, // Optional - no default
|
|
16
|
-
inCollection: { type: 'array', items: { type: 'string' } }
|
|
17
|
-
contextKey: `` // Optional - context key for encryption
|
|
16
|
+
inCollection: { type: 'array', items: { type: 'string' } } // Optional - array of chat IDs, or can be boolean true/false (normalized to ['*'] or removed)
|
|
18
17
|
},
|
|
19
18
|
model: 'oai-gpt4o',
|
|
20
19
|
isMutation: true, // Declaratively mark this as a Mutation
|
|
21
20
|
|
|
22
21
|
resolver: async (_parent, args, _contextValue, _info) => {
|
|
23
|
-
const {
|
|
22
|
+
const { agentContext, hash, displayFilename, tags, notes, mimeType, permanent, inCollection } = args;
|
|
23
|
+
|
|
24
|
+
const defaultCtx = getDefaultContext(agentContext);
|
|
25
|
+
if (!defaultCtx) {
|
|
26
|
+
return JSON.stringify({
|
|
27
|
+
success: false,
|
|
28
|
+
error: 'agentContext with at least one default context is required'
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const contextId = defaultCtx.contextId;
|
|
32
|
+
const contextKey = defaultCtx.contextKey || null;
|
|
24
33
|
|
|
25
34
|
// Validate required parameters
|
|
26
|
-
if (!
|
|
35
|
+
if (!hash) {
|
|
27
36
|
return JSON.stringify({
|
|
28
37
|
success: false,
|
|
29
|
-
error: '
|
|
38
|
+
error: 'hash is required'
|
|
30
39
|
});
|
|
31
40
|
}
|
|
32
41
|
|
|
@@ -63,7 +63,9 @@ export default {
|
|
|
63
63
|
inputParameters: {
|
|
64
64
|
privateData: false,
|
|
65
65
|
chatHistory: [{role: '', content: []}],
|
|
66
|
-
|
|
66
|
+
agentContext: [
|
|
67
|
+
{ contextId: ``, contextKey: ``, default: true }
|
|
68
|
+
],
|
|
67
69
|
chatId: ``,
|
|
68
70
|
language: "English",
|
|
69
71
|
aiName: "Jarvis",
|
|
@@ -76,8 +78,7 @@ export default {
|
|
|
76
78
|
entityId: ``,
|
|
77
79
|
researchMode: false,
|
|
78
80
|
userInfo: '',
|
|
79
|
-
model: 'oai-gpt41'
|
|
80
|
-
contextKey: ``
|
|
81
|
+
model: 'oai-gpt41'
|
|
81
82
|
},
|
|
82
83
|
timeout: 600,
|
|
83
84
|
|
|
@@ -547,10 +548,11 @@ export default {
|
|
|
547
548
|
args.chatHistory = args.chatHistory.slice(-20);
|
|
548
549
|
}
|
|
549
550
|
|
|
550
|
-
//
|
|
551
|
-
// Files
|
|
551
|
+
// Process files in chat history:
|
|
552
|
+
// - Files in collection (all agentContext contexts): stripped, accessible via tools
|
|
553
|
+
// - Files not in collection: left in message for model to see directly
|
|
552
554
|
const { chatHistory: strippedHistory, availableFiles } = await syncAndStripFilesFromChatHistory(
|
|
553
|
-
args.chatHistory, args.
|
|
555
|
+
args.chatHistory, args.agentContext
|
|
554
556
|
);
|
|
555
557
|
args.chatHistory = strippedHistory;
|
|
556
558
|
|
|
@@ -81,12 +81,12 @@ export default {
|
|
|
81
81
|
|
|
82
82
|
executePathway: async ({args, resolver}) => {
|
|
83
83
|
try {
|
|
84
|
-
const { codingTask, userMessage, inputFiles, codingTaskKeywords
|
|
84
|
+
const { codingTask, userMessage, inputFiles, codingTaskKeywords } = args;
|
|
85
85
|
|
|
86
86
|
let taskSuffix = "";
|
|
87
87
|
if (inputFiles) {
|
|
88
|
-
if (!
|
|
89
|
-
throw new Error("
|
|
88
|
+
if (!args.agentContext || !Array.isArray(args.agentContext) || args.agentContext.length === 0) {
|
|
89
|
+
throw new Error("agentContext is required when using the 'inputFiles' parameter. Use ListFileCollection or SearchFileCollection to find available files.");
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
// Resolve file parameters to URLs
|
|
@@ -100,7 +100,7 @@ export default {
|
|
|
100
100
|
|
|
101
101
|
for (const fileRef of fileReferences) {
|
|
102
102
|
// Try to resolve each file reference
|
|
103
|
-
const resolvedUrl = await resolveFileParameter(fileRef,
|
|
103
|
+
const resolvedUrl = await resolveFileParameter(fileRef, args.agentContext);
|
|
104
104
|
if (resolvedUrl) {
|
|
105
105
|
resolvedUrls.push(resolvedUrl);
|
|
106
106
|
} else {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Entity tool that modifies existing files by replacing line ranges or exact string matches
|
|
3
3
|
import logger from '../../../../lib/logger.js';
|
|
4
4
|
import { axios } from '../../../../lib/requestExecutor.js';
|
|
5
|
-
import { uploadFileToCloud, findFileInCollection,
|
|
5
|
+
import { uploadFileToCloud, findFileInCollection, loadMergedFileCollection, getDefaultContext, getMimeTypeFromFilename, deleteFileByHash, isTextMimeType, updateFileMetadata, writeFileDataToRedis, invalidateFileCollectionCache, getActualContentMimeType } from '../../../../lib/fileUtils.js';
|
|
6
6
|
|
|
7
7
|
// Maximum file size for editing (50MB) - prevents memory blowup on huge files
|
|
8
8
|
const MAX_EDITABLE_FILE_SIZE = 50 * 1024 * 1024;
|
|
@@ -145,7 +145,19 @@ export default {
|
|
|
145
145
|
],
|
|
146
146
|
|
|
147
147
|
executePathway: async ({args, runAllPrompts, resolver}) => {
|
|
148
|
-
const { file, startLine, endLine, content, oldString, newString, replaceAll = false,
|
|
148
|
+
const { file, startLine, endLine, content, oldString, newString, replaceAll = false, agentContext } = args;
|
|
149
|
+
|
|
150
|
+
const defaultCtx = getDefaultContext(agentContext);
|
|
151
|
+
if (!defaultCtx) {
|
|
152
|
+
const errorResult = {
|
|
153
|
+
success: false,
|
|
154
|
+
error: "agentContext with at least one default context is required"
|
|
155
|
+
};
|
|
156
|
+
resolver.tool = JSON.stringify({ toolUsed: "EditFile" });
|
|
157
|
+
return JSON.stringify(errorResult);
|
|
158
|
+
}
|
|
159
|
+
const contextId = defaultCtx.contextId;
|
|
160
|
+
const contextKey = defaultCtx.contextKey || null;
|
|
149
161
|
|
|
150
162
|
// Determine which tool was called based on parameters
|
|
151
163
|
const isSearchReplace = oldString !== undefined && newString !== undefined;
|
|
@@ -162,14 +174,6 @@ export default {
|
|
|
162
174
|
return JSON.stringify(errorResult);
|
|
163
175
|
}
|
|
164
176
|
|
|
165
|
-
if (!contextId) {
|
|
166
|
-
const errorResult = {
|
|
167
|
-
success: false,
|
|
168
|
-
error: "contextId is required for file modification"
|
|
169
|
-
};
|
|
170
|
-
resolver.tool = JSON.stringify({ toolUsed: toolName });
|
|
171
|
-
return JSON.stringify(errorResult);
|
|
172
|
-
}
|
|
173
177
|
|
|
174
178
|
// Validate that we have the right parameters for the tool being used
|
|
175
179
|
if (!isSearchReplace && !isEditByLine) {
|
|
@@ -243,7 +247,7 @@ export default {
|
|
|
243
247
|
|
|
244
248
|
try {
|
|
245
249
|
// Resolve file ID first (needed for serialization)
|
|
246
|
-
const collection = await
|
|
250
|
+
const collection = await loadMergedFileCollection(agentContext);
|
|
247
251
|
const foundFile = findFileInCollection(file, collection);
|
|
248
252
|
|
|
249
253
|
if (!foundFile) {
|
|
@@ -281,7 +285,7 @@ export default {
|
|
|
281
285
|
logger.info(`Using cached content for: ${currentFile.displayFilename || file}`);
|
|
282
286
|
} else {
|
|
283
287
|
// First edit in session: load collection and download file
|
|
284
|
-
const currentCollection = await
|
|
288
|
+
const currentCollection = await loadMergedFileCollection(agentContext);
|
|
285
289
|
currentFile = findFileInCollection(file, currentCollection);
|
|
286
290
|
|
|
287
291
|
if (!currentFile) {
|
|
@@ -467,7 +471,7 @@ export default {
|
|
|
467
471
|
if (editResult._isLastOperation) {
|
|
468
472
|
// Flush: upload the final content and update metadata
|
|
469
473
|
const { modifiedContent, currentFile, fileIdToUpdate: initialFileId, filename, mimeType,
|
|
470
|
-
modificationInfo, message,
|
|
474
|
+
modificationInfo, message, resolver: res,
|
|
471
475
|
file: fileParam, isEditByLine: isByLine, isSearchReplace: isSR, replaceAll: repAll,
|
|
472
476
|
startLine: sLine, endLine: eLine } = editResult;
|
|
473
477
|
|
|
@@ -482,7 +486,7 @@ export default {
|
|
|
482
486
|
mimeType,
|
|
483
487
|
filename,
|
|
484
488
|
res,
|
|
485
|
-
|
|
489
|
+
contextId
|
|
486
490
|
);
|
|
487
491
|
|
|
488
492
|
if (!uploadResult || !uploadResult.url) {
|
|
@@ -490,7 +494,8 @@ export default {
|
|
|
490
494
|
}
|
|
491
495
|
|
|
492
496
|
// Update the file collection entry directly (atomic operation)
|
|
493
|
-
|
|
497
|
+
// Use default context from agentContext for consistency
|
|
498
|
+
const latestCollection = await loadMergedFileCollection(agentContext);
|
|
494
499
|
let fileToUpdate = latestCollection.find(f => f.id === fileIdToUpdate);
|
|
495
500
|
|
|
496
501
|
// If not found by ID, try to find by the original file parameter
|
|
@@ -512,7 +517,7 @@ export default {
|
|
|
512
517
|
const { getRedisClient } = await import('../../../../lib/fileUtils.js');
|
|
513
518
|
const redisClient = await getRedisClient();
|
|
514
519
|
if (redisClient) {
|
|
515
|
-
const contextMapKey = `FileStoreMap:ctx:${
|
|
520
|
+
const contextMapKey = `FileStoreMap:ctx:${contextId}`;
|
|
516
521
|
|
|
517
522
|
const existingDataStr = await redisClient.hget(contextMapKey, uploadResult.hash);
|
|
518
523
|
let existingData = {};
|
|
@@ -541,21 +546,21 @@ export default {
|
|
|
541
546
|
permanent: fileToUpdate.permanent || false
|
|
542
547
|
};
|
|
543
548
|
|
|
544
|
-
await writeFileDataToRedis(redisClient, contextMapKey, uploadResult.hash, fileData,
|
|
549
|
+
await writeFileDataToRedis(redisClient, contextMapKey, uploadResult.hash, fileData, contextKey);
|
|
545
550
|
|
|
546
551
|
if (oldHashToDelete && oldHashToDelete !== uploadResult.hash) {
|
|
547
552
|
await redisClient.hdel(contextMapKey, oldHashToDelete);
|
|
548
553
|
}
|
|
549
554
|
|
|
550
|
-
invalidateFileCollectionCache(
|
|
555
|
+
invalidateFileCollectionCache(contextId, contextKey);
|
|
551
556
|
}
|
|
552
557
|
} else if (fileToUpdate.hash) {
|
|
553
|
-
await updateFileMetadata(
|
|
558
|
+
await updateFileMetadata(contextId, fileToUpdate.hash, {
|
|
554
559
|
filename: filename,
|
|
555
560
|
lastAccessed: new Date().toISOString()
|
|
556
|
-
},
|
|
561
|
+
}, contextKey);
|
|
557
562
|
|
|
558
|
-
invalidateFileCollectionCache(
|
|
563
|
+
invalidateFileCollectionCache(contextId, contextKey);
|
|
559
564
|
}
|
|
560
565
|
|
|
561
566
|
// Delete old file version (fire-and-forget)
|
|
@@ -563,7 +568,7 @@ export default {
|
|
|
563
568
|
(async () => {
|
|
564
569
|
try {
|
|
565
570
|
logger.info(`Deleting old file version with hash ${oldHashToDelete} (background task)`);
|
|
566
|
-
await deleteFileByHash(oldHashToDelete, res,
|
|
571
|
+
await deleteFileByHash(oldHashToDelete, res, contextId);
|
|
567
572
|
} catch (cleanupError) {
|
|
568
573
|
logger.warn(`Failed to cleanup old file version: ${cleanupError.message}`);
|
|
569
574
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Uses Redis hash maps (FileStoreMap:ctx:<contextId>) for storage
|
|
4
4
|
// Supports atomic rename/tag/notes updates via UpdateFileMetadata
|
|
5
5
|
import logger from '../../../../lib/logger.js';
|
|
6
|
-
import { addFileToCollection, loadFileCollection, findFileInCollection, deleteFileByHash, updateFileMetadata, invalidateFileCollectionCache } from '../../../../lib/fileUtils.js';
|
|
6
|
+
import { addFileToCollection, loadFileCollection, loadMergedFileCollection, findFileInCollection, deleteFileByHash, updateFileMetadata, invalidateFileCollectionCache, getDefaultContext } from '../../../../lib/fileUtils.js';
|
|
7
7
|
|
|
8
8
|
export default {
|
|
9
9
|
prompt: [],
|
|
@@ -198,7 +198,12 @@ export default {
|
|
|
198
198
|
],
|
|
199
199
|
|
|
200
200
|
executePathway: async ({args, runAllPrompts, resolver}) => {
|
|
201
|
-
const
|
|
201
|
+
const defaultCtx = getDefaultContext(args.agentContext);
|
|
202
|
+
if (!defaultCtx) {
|
|
203
|
+
throw new Error("agentContext with at least one default context is required");
|
|
204
|
+
}
|
|
205
|
+
const contextId = defaultCtx.contextId;
|
|
206
|
+
const contextKey = defaultCtx.contextKey || null;
|
|
202
207
|
|
|
203
208
|
// Determine which function was called based on which parameters are present
|
|
204
209
|
// Order matters: check most specific operations first
|
|
@@ -359,12 +364,12 @@ export default {
|
|
|
359
364
|
const safeFilterTags = Array.isArray(filterTags) ? filterTags : [];
|
|
360
365
|
const queryLower = query.toLowerCase();
|
|
361
366
|
|
|
362
|
-
//
|
|
363
|
-
const
|
|
367
|
+
// Load primary collection for lastAccessed updates (only update files in primary context)
|
|
368
|
+
const primaryFiles = await loadFileCollection(contextId, contextKey, false);
|
|
364
369
|
const now = new Date().toISOString();
|
|
365
370
|
|
|
366
|
-
// Find matching files and update lastAccessed directly
|
|
367
|
-
for (const file of
|
|
371
|
+
// Find matching files in primary collection and update lastAccessed directly
|
|
372
|
+
for (const file of primaryFiles) {
|
|
368
373
|
if (!file.hash) continue;
|
|
369
374
|
|
|
370
375
|
// Fallback to filename if displayFilename is not set (for files uploaded before displayFilename was added)
|
|
@@ -387,8 +392,8 @@ export default {
|
|
|
387
392
|
}
|
|
388
393
|
}
|
|
389
394
|
|
|
390
|
-
//
|
|
391
|
-
const updatedFiles = await
|
|
395
|
+
// Load merged collection for search results (includes all agentContext files)
|
|
396
|
+
const updatedFiles = await loadMergedFileCollection(args.agentContext);
|
|
392
397
|
|
|
393
398
|
// Filter and sort results (for display only, not modifying)
|
|
394
399
|
let results = updatedFiles.filter(file => {
|
|
@@ -560,8 +565,8 @@ export default {
|
|
|
560
565
|
// List collection (read-only, no locking needed)
|
|
561
566
|
const { tags: filterTags = [], sortBy = 'date', limit = 50 } = args;
|
|
562
567
|
|
|
563
|
-
// Use
|
|
564
|
-
const collection = await
|
|
568
|
+
// Use merged collection to include files from all agentContext contexts
|
|
569
|
+
const collection = await loadMergedFileCollection(args.agentContext);
|
|
565
570
|
let results = collection;
|
|
566
571
|
|
|
567
572
|
// Filter by tags if provided
|
|
@@ -113,7 +113,7 @@ export default {
|
|
|
113
113
|
|
|
114
114
|
for (let i = 0; i < imagesToProcess.length; i++) {
|
|
115
115
|
const imageRef = imagesToProcess[i];
|
|
116
|
-
const resolved = await resolveFileParameter(imageRef, args.
|
|
116
|
+
const resolved = await resolveFileParameter(imageRef, args.agentContext);
|
|
117
117
|
if (!resolved) {
|
|
118
118
|
throw new Error(`File not found: "${imageRef}". Use ListFileCollection or SearchFileCollection to find available files.`);
|
|
119
119
|
}
|
|
@@ -109,7 +109,7 @@ export default {
|
|
|
109
109
|
|
|
110
110
|
for (let i = 0; i < imagesToProcess.length; i++) {
|
|
111
111
|
const imageRef = imagesToProcess[i];
|
|
112
|
-
const resolved = await resolveFileParameter(imageRef, args.
|
|
112
|
+
const resolved = await resolveFileParameter(imageRef, args.agentContext, { preferGcs: true });
|
|
113
113
|
if (!resolved) {
|
|
114
114
|
throw new Error(`File not found: "${imageRef}". Use ListFileCollection or SearchFileCollection to find available files.`);
|
|
115
115
|
}
|
|
@@ -135,20 +135,20 @@ export default {
|
|
|
135
135
|
const MAX_CHARS = 100000;
|
|
136
136
|
const MAX_LINES = 1000;
|
|
137
137
|
|
|
138
|
-
let { cloudUrl, file, startChar, endChar, startLine, endLine
|
|
138
|
+
let { cloudUrl, file, startChar, endChar, startLine, endLine } = args;
|
|
139
139
|
|
|
140
140
|
// If file parameter is provided, resolve it to a URL using the common utility
|
|
141
141
|
if (file) {
|
|
142
|
-
if (!
|
|
142
|
+
if (!args.agentContext || !Array.isArray(args.agentContext) || args.agentContext.length === 0) {
|
|
143
143
|
const errorResult = {
|
|
144
144
|
success: false,
|
|
145
|
-
error: "
|
|
145
|
+
error: "agentContext is required when using the 'file' parameter. Use ListFileCollection or SearchFileCollection to find available files."
|
|
146
146
|
};
|
|
147
147
|
resolver.tool = JSON.stringify({ toolUsed: "ReadFile" });
|
|
148
148
|
return JSON.stringify(errorResult);
|
|
149
149
|
}
|
|
150
150
|
// Use useCache: false to ensure we get the latest file data (important after edits)
|
|
151
|
-
const resolvedUrl = await resolveFileParameter(file,
|
|
151
|
+
const resolvedUrl = await resolveFileParameter(file, args.agentContext, { useCache: false });
|
|
152
152
|
if (!resolvedUrl) {
|
|
153
153
|
const errorResult = {
|
|
154
154
|
success: false,
|
|
@@ -80,7 +80,7 @@ export default {
|
|
|
80
80
|
|
|
81
81
|
for (let i = 0; i < imagesToProcess.length; i++) {
|
|
82
82
|
const imageRef = imagesToProcess[i];
|
|
83
|
-
const resolved = await resolveFileParameter(imageRef, args.
|
|
83
|
+
const resolved = await resolveFileParameter(imageRef, args.agentContext, { preferGcs: true });
|
|
84
84
|
if (!resolved) {
|
|
85
85
|
throw new Error(`File not found: "${imageRef}". Use ListFileCollection or SearchFileCollection to find available files.`);
|
|
86
86
|
}
|
|
@@ -128,7 +128,7 @@ export default {
|
|
|
128
128
|
throw new Error("contextId is required when using the 'inputImage' parameter. Use ListFileCollection or SearchFileCollection to find available files.");
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
const resolved = await resolveFileParameter(args.inputImage, args.
|
|
131
|
+
const resolved = await resolveFileParameter(args.inputImage, args.agentContext, { preferGcs: true });
|
|
132
132
|
if (!resolved) {
|
|
133
133
|
throw new Error(`File not found: "${args.inputImage}". Use ListFileCollection or SearchFileCollection to find available files.`);
|
|
134
134
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// sys_tool_view_image.js
|
|
2
2
|
// Tool pathway that allows agents to view image files from the file collection
|
|
3
3
|
import logger from '../../../../lib/logger.js';
|
|
4
|
-
import {
|
|
4
|
+
import { loadMergedFileCollection, findFileInCollection, ensureShortLivedUrl, getDefaultContext } from '../../../../lib/fileUtils.js';
|
|
5
5
|
import { config } from '../../../../config.js';
|
|
6
6
|
|
|
7
7
|
export default {
|
|
@@ -34,15 +34,19 @@ export default {
|
|
|
34
34
|
},
|
|
35
35
|
|
|
36
36
|
executePathway: async ({args, runAllPrompts, resolver}) => {
|
|
37
|
-
const { files
|
|
37
|
+
const { files } = args;
|
|
38
38
|
|
|
39
39
|
if (!files || !Array.isArray(files) || files.length === 0) {
|
|
40
40
|
throw new Error("Files parameter is required and must be a non-empty array");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
if (!args.agentContext || !Array.isArray(args.agentContext) || args.agentContext.length === 0) {
|
|
44
|
+
throw new Error("agentContext is required");
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
try {
|
|
44
|
-
// Load the file collection
|
|
45
|
-
const collection = await
|
|
48
|
+
// Load the file collection (merged from all agentContext contexts)
|
|
49
|
+
const collection = await loadMergedFileCollection(args.agentContext);
|
|
46
50
|
|
|
47
51
|
const imageUrls = [];
|
|
48
52
|
const errors = [];
|
|
@@ -70,7 +74,8 @@ export default {
|
|
|
70
74
|
|
|
71
75
|
// Resolve to short-lived URL if possible
|
|
72
76
|
const fileHandlerUrl = config.get('whisperMediaApiUrl');
|
|
73
|
-
const
|
|
77
|
+
const defaultCtx = getDefaultContext(args.agentContext);
|
|
78
|
+
const fileWithShortLivedUrl = await ensureShortLivedUrl(foundFile, fileHandlerUrl, defaultCtx?.contextId || null);
|
|
74
79
|
|
|
75
80
|
// Add to imageUrls array
|
|
76
81
|
imageUrls.push({
|
|
@@ -87,14 +87,57 @@ const executePathwayWithFallback = async (pathway, pathwayArgs, contextValue, in
|
|
|
87
87
|
if (cortexPathwayName) {
|
|
88
88
|
// Use the specific cortex pathway
|
|
89
89
|
// Transform parameters for cortex pathway
|
|
90
|
-
// Spread all pathway args first
|
|
90
|
+
// Spread all pathway args first, then override specific fields
|
|
91
91
|
const cortexArgs = {
|
|
92
|
-
...pathwayArgs, // Spread all pathway args
|
|
92
|
+
...pathwayArgs, // Spread all pathway args
|
|
93
93
|
model: pathway.model || pathwayArgs.model || "labeeb-agent", // Use pathway model or default
|
|
94
94
|
chatHistory: pathwayArgs.chatHistory ? JSON.parse(JSON.stringify(pathwayArgs.chatHistory)) : [],
|
|
95
95
|
systemPrompt: pathway.systemPrompt || pathwayArgs.systemPrompt
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
+
// Transform old parameters to new format for run_workspace_agent
|
|
99
|
+
if (cortexPathwayName === 'run_workspace_agent') {
|
|
100
|
+
// Remove old aiStyle parameter (no longer used)
|
|
101
|
+
delete cortexArgs.aiStyle;
|
|
102
|
+
|
|
103
|
+
// Transform context parameters to agentContext array format (only if agentContext not already provided)
|
|
104
|
+
if (!cortexArgs.agentContext && (cortexArgs.contextId || cortexArgs.contextKey || cortexArgs.altContextId || cortexArgs.altContextKey)) {
|
|
105
|
+
const agentContext = [];
|
|
106
|
+
|
|
107
|
+
// Add primary context if present
|
|
108
|
+
if (cortexArgs.contextId) {
|
|
109
|
+
agentContext.push({
|
|
110
|
+
contextId: cortexArgs.contextId,
|
|
111
|
+
contextKey: cortexArgs.contextKey || null,
|
|
112
|
+
default: true
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Add alternate context if present
|
|
117
|
+
if (cortexArgs.altContextId) {
|
|
118
|
+
agentContext.push({
|
|
119
|
+
contextId: cortexArgs.altContextId,
|
|
120
|
+
contextKey: cortexArgs.altContextKey || null,
|
|
121
|
+
default: false
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// If we have at least one context, set agentContext and remove old params
|
|
126
|
+
if (agentContext.length > 0) {
|
|
127
|
+
cortexArgs.agentContext = agentContext;
|
|
128
|
+
delete cortexArgs.contextId;
|
|
129
|
+
delete cortexArgs.contextKey;
|
|
130
|
+
delete cortexArgs.altContextId;
|
|
131
|
+
delete cortexArgs.altContextKey;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Ensure researchMode defaults to false if not provided
|
|
136
|
+
if (cortexArgs.researchMode === undefined) {
|
|
137
|
+
cortexArgs.researchMode = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
98
141
|
// If we have text parameter, we need to add it to the chatHistory
|
|
99
142
|
if (pathwayArgs.text) {
|
|
100
143
|
// Find the last user message or create a new one
|
|
@@ -367,6 +367,24 @@ class PathwayResolver {
|
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
async executePathway(args) {
|
|
370
|
+
// Bidirectional context transformation for backward compatibility:
|
|
371
|
+
// 1. If agentContext provided: extract contextId/contextKey for legacy pathways
|
|
372
|
+
// 2. If contextId provided without agentContext: create agentContext for new pathways
|
|
373
|
+
if (args.agentContext && Array.isArray(args.agentContext) && args.agentContext.length > 0) {
|
|
374
|
+
const defaultCtx = args.agentContext.find(ctx => ctx.default) || args.agentContext[0];
|
|
375
|
+
if (defaultCtx) {
|
|
376
|
+
args.contextId = defaultCtx.contextId;
|
|
377
|
+
args.contextKey = defaultCtx.contextKey || null;
|
|
378
|
+
}
|
|
379
|
+
} else if (args.contextId && !args.agentContext) {
|
|
380
|
+
// Backward compat: create agentContext from legacy contextId/contextKey
|
|
381
|
+
args.agentContext = [{
|
|
382
|
+
contextId: args.contextId,
|
|
383
|
+
contextKey: args.contextKey || null,
|
|
384
|
+
default: true
|
|
385
|
+
}];
|
|
386
|
+
}
|
|
387
|
+
|
|
370
388
|
if (this.pathway.executePathway && typeof this.pathway.executePathway === 'function') {
|
|
371
389
|
return await this.pathway.executePathway({ args, runAllPrompts: this.promptAndParse.bind(this), resolver: this });
|
|
372
390
|
}
|
|
@@ -321,6 +321,24 @@ class ReplicateApiPlugin extends ModelPlugin {
|
|
|
321
321
|
};
|
|
322
322
|
break;
|
|
323
323
|
}
|
|
324
|
+
case "replicate-seedance-1.5-pro": {
|
|
325
|
+
const validRatios = ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "9:21"];
|
|
326
|
+
|
|
327
|
+
requestParameters = {
|
|
328
|
+
input: {
|
|
329
|
+
prompt: modelPromptText,
|
|
330
|
+
aspect_ratio: validRatios.includes(combinedParameters.aspectRatio) ? combinedParameters.aspectRatio : "16:9",
|
|
331
|
+
duration: Math.min(12, Math.max(2, combinedParameters.duration || 5)),
|
|
332
|
+
fps: 24,
|
|
333
|
+
camera_fixed: combinedParameters.camera_fixed || false,
|
|
334
|
+
generate_audio: combinedParameters.generate_audio || false,
|
|
335
|
+
...(combinedParameters.seed && Number.isInteger(combinedParameters.seed) && combinedParameters.seed > 0 ? { seed: combinedParameters.seed } : {}),
|
|
336
|
+
...(combinedParameters.image ? { image: combinedParameters.image } : {}),
|
|
337
|
+
...(combinedParameters.image && combinedParameters.last_frame_image ? { last_frame_image: combinedParameters.last_frame_image } : {}),
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
324
342
|
case "replicate-seedream-4": {
|
|
325
343
|
const validSizes = ["1K", "2K", "4K", "custom"];
|
|
326
344
|
const validRatios = ["1:1", "4:3", "3:4", "16:9", "9:16", "match_input_image"];
|
package/server/typeDef.js
CHANGED
|
@@ -58,6 +58,10 @@ const getGraphQlType = (value) => {
|
|
|
58
58
|
const items = schema.items || {};
|
|
59
59
|
const def = schema.default;
|
|
60
60
|
const defaultArray = Array.isArray(def) ? JSON.stringify(def) : '[]';
|
|
61
|
+
// Support explicit object type name (e.g., items: { objType: 'AgentContextInput' })
|
|
62
|
+
if (items.objType) {
|
|
63
|
+
return { type: `[${items.objType}]`, defaultValue: `"${defaultArray.replace(/"/g, '\\"')}"` };
|
|
64
|
+
}
|
|
61
65
|
if (items.type === 'string') {
|
|
62
66
|
return { type: '[String]', defaultValue: defaultArray };
|
|
63
67
|
}
|
|
@@ -103,6 +107,10 @@ const getGraphQlType = (value) => {
|
|
|
103
107
|
if (Array.isArray(value[0]?.content)) {
|
|
104
108
|
return {type: '[MultiMessage]', defaultValue: `"${JSON.stringify(value).replace(/"/g, '\\"')}"`};
|
|
105
109
|
}
|
|
110
|
+
// Check if it's AgentContextInput (has contextId and default properties)
|
|
111
|
+
else if (value[0] && typeof value[0] === 'object' && 'contextId' in value[0] && 'default' in value[0]) {
|
|
112
|
+
return {type: '[AgentContextInput]', defaultValue: `"${JSON.stringify(value).replace(/"/g, '\\"')}"`};
|
|
113
|
+
}
|
|
106
114
|
else {
|
|
107
115
|
return {type: '[Message]', defaultValue: `"${JSON.stringify(value).replace(/"/g, '\\"')}"`};
|
|
108
116
|
}
|
|
@@ -123,8 +131,9 @@ const getGraphQlType = (value) => {
|
|
|
123
131
|
const getMessageTypeDefs = () => {
|
|
124
132
|
const messageType = `input Message { role: String, content: String, name: String }`;
|
|
125
133
|
const multiMessageType = `input MultiMessage { role: String, content: [String], name: String, tool_calls: [String], tool_call_id: String }`;
|
|
134
|
+
const agentContextType = `input AgentContextInput { contextId: String, contextKey: String, default: Boolean }`;
|
|
126
135
|
|
|
127
|
-
return `${messageType}\n\n${multiMessageType}`;
|
|
136
|
+
return `${messageType}\n\n${multiMessageType}\n\n${agentContextType}`;
|
|
128
137
|
};
|
|
129
138
|
|
|
130
139
|
const getPathwayTypeDef = (name, returnType) => {
|