@aj-archipelago/cortex 1.3.66 → 1.3.67
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-autogen2/Dockerfile +88 -21
- package/helper-apps/cortex-autogen2/docker-compose.yml +15 -8
- package/helper-apps/cortex-autogen2/host.json +5 -0
- package/helper-apps/cortex-autogen2/pyproject.toml +82 -25
- package/helper-apps/cortex-autogen2/requirements.txt +84 -14
- package/helper-apps/cortex-autogen2/services/redis_publisher.py +129 -3
- package/helper-apps/cortex-autogen2/task_processor.py +432 -116
- package/helper-apps/cortex-autogen2/tools/__init__.py +2 -0
- package/helper-apps/cortex-autogen2/tools/azure_blob_tools.py +32 -0
- package/helper-apps/cortex-autogen2/tools/azure_foundry_agents.py +50 -14
- package/helper-apps/cortex-autogen2/tools/file_tools.py +169 -44
- package/helper-apps/cortex-autogen2/tools/google_cse.py +117 -0
- package/helper-apps/cortex-autogen2/tools/search_tools.py +655 -98
- package/lib/pathwayManager.js +42 -8
- package/lib/util.js +57 -1
- package/package.json +1 -1
- package/pathways/system/workspaces/run_workspace_prompt.js +0 -3
- package/server/executeWorkspace.js +381 -0
- package/server/graphql.js +5 -180
- package/tests/unit/core/parser.test.js +0 -1
- package/tests/unit/core/pathwayManagerWithFiles.test.js +256 -0
- package/tests/unit/graphql_executeWorkspace_transformation.test.js +244 -0
- package/tests/unit/server/graphql.test.js +122 -1
package/lib/pathwayManager.js
CHANGED
|
@@ -338,36 +338,58 @@ class PathwayManager {
|
|
|
338
338
|
|
|
339
339
|
/**
|
|
340
340
|
* Creates a Prompt object from a prompt item and system prompt.
|
|
341
|
-
* @param {(string|Object)} promptItem - The prompt item (string or {name, prompt} object).
|
|
341
|
+
* @param {(string|Object)} promptItem - The prompt item (string or {name, prompt, files} object).
|
|
342
342
|
* @param {string} systemPrompt - The system prompt to prepend.
|
|
343
343
|
* @param {string} [defaultName] - Default name to use if promptItem is a string or if the object doesn't have a name.
|
|
344
344
|
* @returns {Prompt} A new Prompt object.
|
|
345
345
|
*/
|
|
346
346
|
_createPromptObject(promptItem, systemPrompt, defaultName = null) {
|
|
347
|
-
// Handle both old format (strings) and new format (objects with name and
|
|
347
|
+
// Handle both old format (strings) and new format (objects with name, prompt, and files)
|
|
348
348
|
const promptText = typeof promptItem === 'string' ? promptItem : promptItem.prompt;
|
|
349
349
|
const promptName = typeof promptItem === 'string' ? defaultName : (promptItem.name || defaultName);
|
|
350
|
+
const promptFiles = typeof promptItem === 'string' ? [] : (promptItem.files || []);
|
|
351
|
+
const cortexPathwayName = typeof promptItem === 'string' ? null : (promptItem.cortexPathwayName || null);
|
|
350
352
|
|
|
351
|
-
const messages = [
|
|
352
|
-
// Add the original prompt as a user message
|
|
353
|
-
{ "role": "user", "content": `{{text}}\n\n${promptText}` },
|
|
354
|
-
];
|
|
353
|
+
const messages = [];
|
|
355
354
|
|
|
356
355
|
// Only include system message if systemPrompt has content
|
|
357
356
|
if (systemPrompt && systemPrompt.trim() !== "") {
|
|
358
357
|
messages.unshift({ "role": "system", "content": systemPrompt });
|
|
359
358
|
}
|
|
360
359
|
|
|
361
|
-
|
|
360
|
+
// If there are files, include chatHistory to get the file content, then add user message
|
|
361
|
+
if (promptFiles.length > 0) {
|
|
362
|
+
// Add chatHistory which will contain the resolved file content
|
|
363
|
+
messages.push("{{chatHistory}}");
|
|
364
|
+
// Add the user text and prompt as a separate user message
|
|
365
|
+
messages.push({ "role": "user", "content": `{{text}}\n\n${promptText}` });
|
|
366
|
+
} else {
|
|
367
|
+
// No files, just add the user message with text and prompt
|
|
368
|
+
messages.push({ "role": "user", "content": `{{text}}\n\n${promptText}` });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const prompt = new Prompt({
|
|
362
372
|
name: promptName,
|
|
363
373
|
messages: messages
|
|
364
374
|
});
|
|
375
|
+
|
|
376
|
+
// Store file hashes for later resolution
|
|
377
|
+
if (promptFiles.length > 0) {
|
|
378
|
+
prompt.fileHashes = promptFiles;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Preserve cortexPathwayName if present
|
|
382
|
+
if (cortexPathwayName) {
|
|
383
|
+
prompt.cortexPathwayName = cortexPathwayName;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return prompt;
|
|
365
387
|
}
|
|
366
388
|
|
|
367
389
|
/**
|
|
368
390
|
* Transforms the prompts in a pathway to include the system prompt.
|
|
369
391
|
* @param {Object} pathway - The pathway object to transform.
|
|
370
|
-
* @param {(string[]|Object[])} pathway.prompt - Array of user prompts (strings) or prompt objects with {name, prompt} properties.
|
|
392
|
+
* @param {(string[]|Object[])} pathway.prompt - Array of user prompts (strings) or prompt objects with {name, prompt, files} properties.
|
|
371
393
|
* @param {string} pathway.systemPrompt - The system prompt to prepend to each user prompt.
|
|
372
394
|
* @returns {Object} A new pathway object with transformed prompts.
|
|
373
395
|
*/
|
|
@@ -379,6 +401,16 @@ class PathwayManager {
|
|
|
379
401
|
// Transform each prompt in the array
|
|
380
402
|
newPathway.prompt = prompt.map(p => this._createPromptObject(p, systemPrompt));
|
|
381
403
|
|
|
404
|
+
// Collect all file hashes from all prompts
|
|
405
|
+
const allFileHashes = newPathway.prompt
|
|
406
|
+
.filter(p => p.fileHashes && p.fileHashes.length > 0)
|
|
407
|
+
.flatMap(p => p.fileHashes);
|
|
408
|
+
|
|
409
|
+
// Store file hashes at pathway level for later resolution
|
|
410
|
+
if (allFileHashes.length > 0) {
|
|
411
|
+
newPathway.fileHashes = [...new Set(allFileHashes)]; // Remove duplicates
|
|
412
|
+
}
|
|
413
|
+
|
|
382
414
|
return newPathway;
|
|
383
415
|
}
|
|
384
416
|
|
|
@@ -426,6 +458,8 @@ class PathwayManager {
|
|
|
426
458
|
input PromptInput {
|
|
427
459
|
name: String!
|
|
428
460
|
prompt: String!
|
|
461
|
+
files: [String!]
|
|
462
|
+
cortexPathwayName: String
|
|
429
463
|
}
|
|
430
464
|
|
|
431
465
|
input PathwayInput {
|
package/lib/util.js
CHANGED
|
@@ -412,6 +412,61 @@ function getAvailableFiles(chatHistory) {
|
|
|
412
412
|
return availableFiles;
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Convert file hashes to content format suitable for LLM processing
|
|
417
|
+
* @param {Array<string>} fileHashes - Array of file hashes to resolve
|
|
418
|
+
* @param {Object} config - Configuration object with file service endpoints
|
|
419
|
+
* @returns {Promise<Array<string>>} Array of stringified file content objects
|
|
420
|
+
*/
|
|
421
|
+
async function resolveFileHashesToContent(fileHashes, config) {
|
|
422
|
+
if (!fileHashes || fileHashes.length === 0) return [];
|
|
423
|
+
|
|
424
|
+
const fileContentPromises = fileHashes.map(async (hash) => {
|
|
425
|
+
try {
|
|
426
|
+
// Use the existing file handler (cortex-file-handler) to resolve file hashes
|
|
427
|
+
const fileHandlerUrl = config?.get?.('whisperMediaApiUrl');
|
|
428
|
+
|
|
429
|
+
if (fileHandlerUrl && fileHandlerUrl !== 'null') {
|
|
430
|
+
// Make request to file handler to get file content by hash
|
|
431
|
+
const response = await axios.get(fileHandlerUrl, {
|
|
432
|
+
params: { hash: hash, checkHash: true }
|
|
433
|
+
});
|
|
434
|
+
if (response.status === 200) {
|
|
435
|
+
const fileData = response.data;
|
|
436
|
+
const fileUrl = fileData.shortLivedUrl || fileData.url;
|
|
437
|
+
const convertedUrl = fileData.converted?.url;
|
|
438
|
+
const convertedGcsUrl = fileData.converted?.gcs;
|
|
439
|
+
|
|
440
|
+
return JSON.stringify({
|
|
441
|
+
type: "image_url",
|
|
442
|
+
url: convertedUrl,
|
|
443
|
+
image_url: { url: convertedUrl },
|
|
444
|
+
gcs: convertedGcsUrl || fileData.gcs, // Add GCS URL for Gemini models
|
|
445
|
+
originalFilename: fileData.filename,
|
|
446
|
+
hash: hash
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Fallback: create a placeholder that indicates file resolution is needed
|
|
452
|
+
return JSON.stringify({
|
|
453
|
+
type: "file_hash",
|
|
454
|
+
hash: hash,
|
|
455
|
+
_cortex_needs_resolution: true
|
|
456
|
+
});
|
|
457
|
+
} catch (error) {
|
|
458
|
+
// Return error indicator
|
|
459
|
+
return JSON.stringify({
|
|
460
|
+
type: "file_error",
|
|
461
|
+
hash: hash,
|
|
462
|
+
error: error.message
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
return Promise.all(fileContentPromises);
|
|
468
|
+
}
|
|
469
|
+
|
|
415
470
|
export {
|
|
416
471
|
getUniqueId,
|
|
417
472
|
getSearchResultId,
|
|
@@ -426,5 +481,6 @@ export {
|
|
|
426
481
|
getMediaChunks,
|
|
427
482
|
markCompletedForCleanUp,
|
|
428
483
|
removeOldImageAndFileContent,
|
|
429
|
-
getAvailableFiles
|
|
484
|
+
getAvailableFiles,
|
|
485
|
+
resolveFileHashesToContent
|
|
430
486
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aj-archipelago/cortex",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.67",
|
|
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": {
|
|
@@ -79,9 +79,6 @@ export default {
|
|
|
79
79
|
try {
|
|
80
80
|
let currentMessages = JSON.parse(JSON.stringify(args.chatHistory));
|
|
81
81
|
|
|
82
|
-
console.log("currentMessages", currentMessages);
|
|
83
|
-
console.log("args", args);
|
|
84
|
-
|
|
85
82
|
let response = await runAllPrompts({
|
|
86
83
|
...args,
|
|
87
84
|
chatHistory: currentMessages,
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
// executeWorkspace.js
|
|
2
|
+
// Handles the executeWorkspace GraphQL query resolver and related functionality
|
|
3
|
+
//
|
|
4
|
+
// This module contains the implementation of the executeWorkspace resolver, which is responsible
|
|
5
|
+
// for executing user-defined pathways (workspaces) with various execution modes:
|
|
6
|
+
// - Sequential execution of all prompts (default)
|
|
7
|
+
// - Parallel execution of specific named prompts
|
|
8
|
+
// - Parallel execution of all prompts (wildcard mode)
|
|
9
|
+
//
|
|
10
|
+
// The resolver supports both legacy pathway formats and new dynamic pathways with cortexPathwayName.
|
|
11
|
+
|
|
12
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
13
|
+
import logger from '../lib/logger.js';
|
|
14
|
+
import { callPathway } from '../lib/pathwayTools.js';
|
|
15
|
+
import { getPathwayTypeDef, userPathwayInputParameters } from './typeDef.js';
|
|
16
|
+
|
|
17
|
+
// Helper function to resolve file hashes and add them to chatHistory
|
|
18
|
+
const resolveAndAddFileContent = async (pathways, pathwayArgs, requestId, config) => {
|
|
19
|
+
let fileContentAdded = false;
|
|
20
|
+
|
|
21
|
+
// Check if any pathway has file hashes
|
|
22
|
+
const pathwaysWithFiles = Array.isArray(pathways) ? pathways : [pathways];
|
|
23
|
+
|
|
24
|
+
for (const pathway of pathwaysWithFiles) {
|
|
25
|
+
if (pathway.fileHashes && pathway.fileHashes.length > 0) {
|
|
26
|
+
try {
|
|
27
|
+
const { resolveFileHashesToContent } = await import('../lib/util.js');
|
|
28
|
+
const fileContent = await resolveFileHashesToContent(pathway.fileHashes, config);
|
|
29
|
+
|
|
30
|
+
// Add file content to chatHistory if not already present (only do this once)
|
|
31
|
+
if (!fileContentAdded) {
|
|
32
|
+
// Initialize chatHistory if it doesn't exist
|
|
33
|
+
if (!pathwayArgs.chatHistory) {
|
|
34
|
+
pathwayArgs.chatHistory = [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Find the last user message or create one
|
|
38
|
+
let lastUserMessage = null;
|
|
39
|
+
for (let i = pathwayArgs.chatHistory.length - 1; i >= 0; i--) {
|
|
40
|
+
if (pathwayArgs.chatHistory[i].role === 'user') {
|
|
41
|
+
lastUserMessage = pathwayArgs.chatHistory[i];
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!lastUserMessage) {
|
|
47
|
+
lastUserMessage = {
|
|
48
|
+
role: 'user',
|
|
49
|
+
content: []
|
|
50
|
+
};
|
|
51
|
+
pathwayArgs.chatHistory.push(lastUserMessage);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Ensure content is an array
|
|
55
|
+
if (!Array.isArray(lastUserMessage.content)) {
|
|
56
|
+
lastUserMessage.content = [
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
type: "text",
|
|
59
|
+
text: lastUserMessage.content || ""
|
|
60
|
+
})
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Add file content
|
|
65
|
+
lastUserMessage.content.push(...fileContent);
|
|
66
|
+
fileContentAdded = true;
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.error(`[${requestId}] Failed to resolve file hashes for pathway ${pathway.name || 'unnamed'}: ${error.message}`);
|
|
70
|
+
// Continue execution without files
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Only process files once for multiple pathways
|
|
74
|
+
if (fileContentAdded) break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return fileContentAdded;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Helper function to execute pathway with cortex pathway name or fallback to legacy
|
|
82
|
+
const executePathwayWithFallback = async (pathway, pathwayArgs, contextValue, info, requestId, originalPrompt = null, config) => {
|
|
83
|
+
const cortexPathwayName = (originalPrompt && typeof originalPrompt === 'object' && originalPrompt.cortexPathwayName)
|
|
84
|
+
? originalPrompt.cortexPathwayName
|
|
85
|
+
: null;
|
|
86
|
+
|
|
87
|
+
if (cortexPathwayName) {
|
|
88
|
+
// Use the specific cortex pathway
|
|
89
|
+
// Transform parameters for cortex pathway
|
|
90
|
+
const cortexArgs = {
|
|
91
|
+
model: pathway.model || pathwayArgs.model || "labeeb-agent", // Use pathway model or default
|
|
92
|
+
chatHistory: [],
|
|
93
|
+
systemPrompt: pathway.systemPrompt
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// If we have existing chatHistory, use it as base
|
|
97
|
+
if (pathwayArgs.chatHistory && pathwayArgs.chatHistory.length > 0) {
|
|
98
|
+
cortexArgs.chatHistory = JSON.parse(JSON.stringify(pathwayArgs.chatHistory));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// If we have text parameter, we need to add it to the chatHistory
|
|
102
|
+
if (pathwayArgs.text) {
|
|
103
|
+
// Find the last user message or create a new one
|
|
104
|
+
let lastUserMessage = null;
|
|
105
|
+
for (let i = cortexArgs.chatHistory.length - 1; i >= 0; i--) {
|
|
106
|
+
if (cortexArgs.chatHistory[i].role === 'user') {
|
|
107
|
+
lastUserMessage = cortexArgs.chatHistory[i];
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (lastUserMessage) {
|
|
113
|
+
// Ensure content is an array
|
|
114
|
+
if (!Array.isArray(lastUserMessage.content)) {
|
|
115
|
+
lastUserMessage.content = [JSON.stringify({
|
|
116
|
+
type: "text",
|
|
117
|
+
text: lastUserMessage.content || ""
|
|
118
|
+
})];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Add the text parameter as a text content item
|
|
122
|
+
const textFromPrompt = originalPrompt?.prompt || pathwayArgs.text;
|
|
123
|
+
lastUserMessage.content.unshift(JSON.stringify({
|
|
124
|
+
type: "text",
|
|
125
|
+
text: `${pathwayArgs.text}\n\n${textFromPrompt}`
|
|
126
|
+
}));
|
|
127
|
+
} else {
|
|
128
|
+
// Create new user message with text
|
|
129
|
+
const textFromPrompt = originalPrompt?.prompt || pathwayArgs.text;
|
|
130
|
+
cortexArgs.chatHistory.push({
|
|
131
|
+
role: 'user',
|
|
132
|
+
content: [JSON.stringify({
|
|
133
|
+
type: "text",
|
|
134
|
+
text: `${pathwayArgs.text}\n\n${textFromPrompt}`
|
|
135
|
+
})]
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Create a pathwayResolver to capture extended data like artifacts
|
|
141
|
+
const { PathwayResolver } = await import('./pathwayResolver.js');
|
|
142
|
+
const cortexPathway = config.get(`pathways.${cortexPathwayName}`);
|
|
143
|
+
if (!cortexPathway) {
|
|
144
|
+
throw new Error(`Cortex pathway ${cortexPathwayName} not found`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const pathwayResolver = new PathwayResolver({
|
|
148
|
+
config,
|
|
149
|
+
pathway: cortexPathway,
|
|
150
|
+
args: cortexArgs
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const result = await callPathway(cortexPathwayName, cortexArgs, pathwayResolver);
|
|
154
|
+
|
|
155
|
+
// Extract resultData from pathwayResolver (includes artifacts and other extended data)
|
|
156
|
+
const resultData = pathwayResolver.pathwayResultData
|
|
157
|
+
? JSON.stringify(pathwayResolver.pathwayResultData)
|
|
158
|
+
: null;
|
|
159
|
+
|
|
160
|
+
// Return result with extended data
|
|
161
|
+
return {
|
|
162
|
+
result,
|
|
163
|
+
resultData,
|
|
164
|
+
warnings: pathwayResolver.warnings,
|
|
165
|
+
errors: pathwayResolver.errors
|
|
166
|
+
};
|
|
167
|
+
} else {
|
|
168
|
+
// Fallback to original pathway execution for legacy prompts
|
|
169
|
+
const pathwayContext = { ...contextValue, pathway };
|
|
170
|
+
return await pathway.rootResolver(null, pathwayArgs, pathwayContext, info);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Main executeWorkspace resolver
|
|
175
|
+
export const executeWorkspaceResolver = async (_, args, contextValue, info, config, pathwayManager) => {
|
|
176
|
+
const startTime = Date.now();
|
|
177
|
+
const requestId = uuidv4();
|
|
178
|
+
const { userId, pathwayName, promptNames, ...pathwayArgs } = args;
|
|
179
|
+
|
|
180
|
+
logger.info(`>>> [${requestId}] executeWorkspace started - userId: ${userId}, pathwayName: ${pathwayName}, promptNames: ${promptNames?.join(',') || 'none'}`);
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
contextValue.config = config;
|
|
184
|
+
|
|
185
|
+
// Get the base pathway from the user
|
|
186
|
+
const pathways = await pathwayManager.getLatestPathways();
|
|
187
|
+
|
|
188
|
+
if (!pathways[userId] || !pathways[userId][pathwayName]) {
|
|
189
|
+
const error = new Error(`Pathway '${pathwayName}' not found for user '${userId}'`);
|
|
190
|
+
logger.error(`!!! [${requestId}] ${error.message} - Available users: ${Object.keys(pathways).join(', ')}`);
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const basePathway = pathways[userId][pathwayName];
|
|
195
|
+
|
|
196
|
+
// If promptNames is specified, use getPathways to get individual pathways and execute in parallel
|
|
197
|
+
if (promptNames && promptNames.length > 0) {
|
|
198
|
+
|
|
199
|
+
// Check if the prompts are in legacy format (array of strings)
|
|
200
|
+
// If so, we can't use promptNames filtering and need to ask user to republish
|
|
201
|
+
if (pathwayManager.isLegacyPromptFormat(userId, pathwayName)) {
|
|
202
|
+
const error = new Error(
|
|
203
|
+
`The pathway '${pathwayName}' uses legacy prompt format (array of strings) which doesn't support the promptNames parameter. ` +
|
|
204
|
+
`Please unpublish and republish your workspace to upgrade to the new format that supports named prompts.`
|
|
205
|
+
);
|
|
206
|
+
logger.error(`!!! [${requestId}] ${error.message}`);
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Handle wildcard case - execute all prompts in parallel
|
|
211
|
+
if (promptNames.includes('*')) {
|
|
212
|
+
logger.info(`[${requestId}] Executing all prompts in parallel (wildcard specified)`);
|
|
213
|
+
const individualPathways = await pathwayManager.getPathways(basePathway);
|
|
214
|
+
|
|
215
|
+
if (individualPathways.length === 0) {
|
|
216
|
+
const error = new Error(`No prompts found in pathway '${pathwayName}'`);
|
|
217
|
+
logger.error(`!!! [${requestId}] ${error.message}`);
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Resolve file content for any pathways that have file hashes
|
|
222
|
+
await resolveAndAddFileContent(individualPathways, pathwayArgs, requestId, config);
|
|
223
|
+
|
|
224
|
+
// Execute all pathways in parallel
|
|
225
|
+
const results = await Promise.all(
|
|
226
|
+
individualPathways.map(async (pathway, index) => {
|
|
227
|
+
try {
|
|
228
|
+
// Check if the prompt has a cortexPathwayName (new format)
|
|
229
|
+
const originalPrompt = basePathway.prompt[index];
|
|
230
|
+
|
|
231
|
+
const result = await executePathwayWithFallback(pathway, pathwayArgs, contextValue, info, requestId, originalPrompt, config);
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
result: result.result,
|
|
235
|
+
resultData: result.resultData,
|
|
236
|
+
warnings: result.warnings,
|
|
237
|
+
errors: result.errors,
|
|
238
|
+
promptName: pathway.name || `prompt_${index + 1}`
|
|
239
|
+
};
|
|
240
|
+
} catch (error) {
|
|
241
|
+
logger.error(`!!! [${requestId}] Error in pathway ${index + 1}/${individualPathways.length}: ${pathway.name || 'unnamed'} - ${error.message}`);
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const duration = Date.now() - startTime;
|
|
248
|
+
logger.info(`<<< [${requestId}] executeWorkspace completed successfully in ${duration}ms - returned ${results.length} results`);
|
|
249
|
+
|
|
250
|
+
// Return a single result with JSON stringified array of results
|
|
251
|
+
return {
|
|
252
|
+
debug: `Executed ${results.length} prompts in parallel`,
|
|
253
|
+
result: JSON.stringify(results),
|
|
254
|
+
resultData: null,
|
|
255
|
+
previousResult: null,
|
|
256
|
+
warnings: [],
|
|
257
|
+
errors: [],
|
|
258
|
+
contextId: requestId,
|
|
259
|
+
tool: 'executeWorkspace'
|
|
260
|
+
};
|
|
261
|
+
} else {
|
|
262
|
+
// Handle specific prompt names
|
|
263
|
+
logger.info(`[${requestId}] Executing specific prompts: ${promptNames.join(', ')}`);
|
|
264
|
+
const individualPathways = await pathwayManager.getPathways(basePathway, promptNames);
|
|
265
|
+
|
|
266
|
+
if (individualPathways.length === 0) {
|
|
267
|
+
const error = new Error(`No prompts found matching the specified names: ${promptNames.join(', ')}`);
|
|
268
|
+
logger.error(`!!! [${requestId}] ${error.message}`);
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Resolve file content for any pathways that have file hashes
|
|
273
|
+
await resolveAndAddFileContent(individualPathways, pathwayArgs, requestId, config);
|
|
274
|
+
|
|
275
|
+
// Execute all pathways in parallel
|
|
276
|
+
const results = await Promise.all(
|
|
277
|
+
individualPathways.map(async (pathway, index) => {
|
|
278
|
+
try {
|
|
279
|
+
// Find the original prompt by name to get the cortexPathwayName
|
|
280
|
+
const originalPrompt = basePathway.prompt.find(p =>
|
|
281
|
+
(typeof p === 'object' && p.name === pathway.name) ||
|
|
282
|
+
(typeof p === 'string' && pathway.name === `prompt_${basePathway.prompt.indexOf(p)}`)
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const result = await executePathwayWithFallback(pathway, pathwayArgs, contextValue, info, requestId, originalPrompt, config);
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
result: result.result,
|
|
289
|
+
resultData: result.resultData,
|
|
290
|
+
warnings: result.warnings,
|
|
291
|
+
errors: result.errors,
|
|
292
|
+
promptName: pathway.name || `prompt_${index + 1}`
|
|
293
|
+
};
|
|
294
|
+
} catch (error) {
|
|
295
|
+
logger.error(`!!! [${requestId}] Error in pathway ${index + 1}/${individualPathways.length}: ${pathway.name || 'unnamed'} - ${error.message}`);
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const duration = Date.now() - startTime;
|
|
302
|
+
logger.info(`<<< [${requestId}] executeWorkspace completed successfully in ${duration}ms - returned ${results.length} results`);
|
|
303
|
+
|
|
304
|
+
// Return a single result with JSON stringified array of results (consistent with wildcard case)
|
|
305
|
+
return {
|
|
306
|
+
debug: `Executed ${results.length} specific prompts in parallel: ${promptNames.join(', ')}`,
|
|
307
|
+
result: JSON.stringify(results),
|
|
308
|
+
resultData: null,
|
|
309
|
+
previousResult: null,
|
|
310
|
+
warnings: [],
|
|
311
|
+
errors: [],
|
|
312
|
+
contextId: requestId,
|
|
313
|
+
tool: 'executeWorkspace'
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Default behavior: execute all prompts in sequence
|
|
319
|
+
logger.info(`[${requestId}] Executing prompts in sequence`);
|
|
320
|
+
const userPathway = await pathwayManager.getPathway(userId, pathwayName);
|
|
321
|
+
contextValue.pathway = userPathway;
|
|
322
|
+
|
|
323
|
+
// Handle file hashes if present in the pathway
|
|
324
|
+
await resolveAndAddFileContent(userPathway, pathwayArgs, requestId, config);
|
|
325
|
+
|
|
326
|
+
// Check if any prompt has cortexPathwayName (for dynamic pathways)
|
|
327
|
+
let result;
|
|
328
|
+
if (userPathway.prompt && Array.isArray(userPathway.prompt)) {
|
|
329
|
+
const firstPrompt = userPathway.prompt[0];
|
|
330
|
+
|
|
331
|
+
result = await executePathwayWithFallback(userPathway, pathwayArgs, contextValue, info, requestId, firstPrompt, config);
|
|
332
|
+
} else {
|
|
333
|
+
// No prompt array, use legacy execution
|
|
334
|
+
result = await userPathway.rootResolver(null, pathwayArgs, contextValue, info);
|
|
335
|
+
}
|
|
336
|
+
const duration = Date.now() - startTime;
|
|
337
|
+
logger.info(`<<< [${requestId}] executeWorkspace completed successfully in ${duration}ms - returned 1 result`);
|
|
338
|
+
return result; // Return single result directly
|
|
339
|
+
|
|
340
|
+
} catch (error) {
|
|
341
|
+
const duration = Date.now() - startTime;
|
|
342
|
+
logger.error(`!!! [${requestId}] executeWorkspace failed after ${duration}ms`);
|
|
343
|
+
logger.error(`!!! [${requestId}] Error type: ${error.constructor.name}`);
|
|
344
|
+
logger.error(`!!! [${requestId}] Error message: ${error.message}`);
|
|
345
|
+
logger.error(`!!! [${requestId}] Error stack: ${error.stack}`);
|
|
346
|
+
|
|
347
|
+
// Log additional context for debugging "memory access out of bounds" errors
|
|
348
|
+
if (error.message && error.message.includes('memory')) {
|
|
349
|
+
logger.error(`!!! [${requestId}] MEMORY ERROR DETECTED - Additional context:`);
|
|
350
|
+
logger.error(`!!! [${requestId}] - Node.js version: ${process.version}`);
|
|
351
|
+
logger.error(`!!! [${requestId}] - Memory usage: ${JSON.stringify(process.memoryUsage())}`);
|
|
352
|
+
logger.error(`!!! [${requestId}] - Args size estimate: ${JSON.stringify(args).length} chars`);
|
|
353
|
+
logger.error(`!!! [${requestId}] - PathwayArgs keys: ${Object.keys(pathwayArgs).join(', ')}`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// Type definitions for executeWorkspace
|
|
361
|
+
export const getExecuteWorkspaceTypeDefs = () => {
|
|
362
|
+
return `
|
|
363
|
+
${getPathwayTypeDef('ExecuteWorkspace', 'String')}
|
|
364
|
+
|
|
365
|
+
type ExecuteWorkspaceResult {
|
|
366
|
+
debug: String
|
|
367
|
+
result: String
|
|
368
|
+
resultData: String
|
|
369
|
+
previousResult: String
|
|
370
|
+
warnings: [String]
|
|
371
|
+
errors: [String]
|
|
372
|
+
contextId: String
|
|
373
|
+
tool: String
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
extend type Query {
|
|
377
|
+
executeWorkspace(userId: String!, pathwayName: String!, ${userPathwayInputParameters}): ExecuteWorkspaceResult
|
|
378
|
+
}
|
|
379
|
+
`;
|
|
380
|
+
};
|
|
381
|
+
|