@aj-archipelago/cortex 1.3.65 → 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/entityConstants.js +1 -1
- package/lib/pathwayManager.js +42 -8
- package/lib/pathwayTools.js +3 -3
- package/lib/util.js +58 -2
- package/package.json +1 -1
- package/pathways/system/entity/memory/sys_memory_format.js +1 -0
- package/pathways/system/entity/memory/sys_memory_manager.js +3 -3
- package/pathways/system/entity/sys_entity_start.js +1 -1
- package/pathways/system/entity/tools/sys_tool_bing_search_afagent.js +2 -0
- package/pathways/system/entity/tools/sys_tool_codingagent.js +2 -2
- package/pathways/system/entity/tools/sys_tool_google_search.js +3 -3
- package/pathways/system/entity/tools/sys_tool_grok_x_search.js +12 -2
- package/pathways/system/workspaces/run_workspace_prompt.js +0 -3
- package/server/executeWorkspace.js +381 -0
- package/server/graphql.js +5 -180
- package/server/pathwayResolver.js +3 -3
- package/server/plugins/apptekTranslatePlugin.js +2 -2
- package/server/plugins/azureFoundryAgentsPlugin.js +1 -1
- 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
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PathwayManager File Handling Tests
|
|
3
|
+
*
|
|
4
|
+
* This test suite validates the PathwayManager's file handling functionality, specifically
|
|
5
|
+
* testing how the manager processes and transforms pathway prompts that include file attachments.
|
|
6
|
+
*
|
|
7
|
+
* Key functionality tested:
|
|
8
|
+
* - File hash transformation from 'files' to 'fileHashes' property
|
|
9
|
+
* - Collection and deduplication of file hashes at the pathway level
|
|
10
|
+
* - Backward compatibility with legacy string-based prompts without file attachments
|
|
11
|
+
* - Handling of edge cases (null, undefined, empty file arrays)
|
|
12
|
+
* - Prompt object creation with file metadata
|
|
13
|
+
*
|
|
14
|
+
* The PathwayManager allows prompts to reference files by their hashes, which are then
|
|
15
|
+
* processed and made available to the execution context. This test suite ensures that
|
|
16
|
+
* file metadata is correctly preserved, transformed, and aggregated during pathway processing.
|
|
17
|
+
*
|
|
18
|
+
* Test scenarios covered:
|
|
19
|
+
* 1. Prompts with multiple file attachments
|
|
20
|
+
* 2. Prompts with empty or missing file arrays
|
|
21
|
+
* 3. Legacy string prompts (no file support)
|
|
22
|
+
* 4. Duplicate file hash deduplication
|
|
23
|
+
* 5. Null/undefined file handling
|
|
24
|
+
* 6. Direct prompt object creation with files
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import test from 'ava';
|
|
28
|
+
import PathwayManager from '../../../lib/pathwayManager.js';
|
|
29
|
+
|
|
30
|
+
// Mock config for PathwayManager
|
|
31
|
+
const mockConfig = {
|
|
32
|
+
storageType: 'local',
|
|
33
|
+
filePath: './test-pathways.json',
|
|
34
|
+
publishKey: 'test-key'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Mock base pathway
|
|
38
|
+
const mockBasePathway = {
|
|
39
|
+
name: 'base',
|
|
40
|
+
prompt: '{{text}}',
|
|
41
|
+
systemPrompt: '',
|
|
42
|
+
inputParameters: {},
|
|
43
|
+
typeDef: 'type Test { test: String }',
|
|
44
|
+
rootResolver: () => {},
|
|
45
|
+
resolver: () => {}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Mock storage strategy
|
|
49
|
+
class MockStorageStrategy {
|
|
50
|
+
async load() {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async save(data) {
|
|
55
|
+
// Do nothing
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getLastModified() {
|
|
59
|
+
return Date.now();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test('pathwayManager handles prompt format with files correctly', async t => {
|
|
64
|
+
const pathwayManager = new PathwayManager(mockConfig, mockBasePathway);
|
|
65
|
+
|
|
66
|
+
// Replace storage with mock
|
|
67
|
+
pathwayManager.storage = new MockStorageStrategy();
|
|
68
|
+
|
|
69
|
+
// Test prompts with files
|
|
70
|
+
const pathwayWithFiles = {
|
|
71
|
+
prompt: [
|
|
72
|
+
{
|
|
73
|
+
name: 'Analyze Document',
|
|
74
|
+
prompt: 'Please analyze the provided document',
|
|
75
|
+
files: ['abc123def456', 'def456ghi789']
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'Summarize Text',
|
|
79
|
+
prompt: 'Please summarize the text',
|
|
80
|
+
files: []
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'Simple Task',
|
|
84
|
+
prompt: 'Perform a simple task'
|
|
85
|
+
// No files property
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
systemPrompt: 'You are a helpful assistant'
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Test transformPrompts method
|
|
92
|
+
const transformedPathway = await pathwayManager.transformPrompts(pathwayWithFiles);
|
|
93
|
+
|
|
94
|
+
// Verify the transformed pathway structure
|
|
95
|
+
t.truthy(transformedPathway);
|
|
96
|
+
t.true(Array.isArray(transformedPathway.prompt));
|
|
97
|
+
t.is(transformedPathway.prompt.length, 3);
|
|
98
|
+
|
|
99
|
+
// Verify first prompt with files
|
|
100
|
+
const firstPrompt = transformedPathway.prompt[0];
|
|
101
|
+
t.is(firstPrompt.name, 'Analyze Document');
|
|
102
|
+
t.truthy(firstPrompt.fileHashes);
|
|
103
|
+
t.deepEqual(firstPrompt.fileHashes, ['abc123def456', 'def456ghi789']);
|
|
104
|
+
|
|
105
|
+
// Verify second prompt with empty files
|
|
106
|
+
const secondPrompt = transformedPathway.prompt[1];
|
|
107
|
+
t.is(secondPrompt.name, 'Summarize Text');
|
|
108
|
+
t.falsy(secondPrompt.fileHashes); // Empty array results in no fileHashes property
|
|
109
|
+
|
|
110
|
+
// Verify third prompt without files property
|
|
111
|
+
const thirdPrompt = transformedPathway.prompt[2];
|
|
112
|
+
t.is(thirdPrompt.name, 'Simple Task');
|
|
113
|
+
t.falsy(thirdPrompt.fileHashes);
|
|
114
|
+
|
|
115
|
+
// Verify pathway-level file hashes collection
|
|
116
|
+
t.truthy(transformedPathway.fileHashes);
|
|
117
|
+
t.deepEqual(transformedPathway.fileHashes, ['abc123def456', 'def456ghi789']);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('pathwayManager handles legacy string prompts correctly', async t => {
|
|
121
|
+
const pathwayManager = new PathwayManager(mockConfig, mockBasePathway);
|
|
122
|
+
|
|
123
|
+
// Replace storage with mock
|
|
124
|
+
pathwayManager.storage = new MockStorageStrategy();
|
|
125
|
+
|
|
126
|
+
// Test legacy string prompts
|
|
127
|
+
const legacyPathway = {
|
|
128
|
+
prompt: [
|
|
129
|
+
'Please analyze the data',
|
|
130
|
+
'Summarize the findings'
|
|
131
|
+
],
|
|
132
|
+
systemPrompt: 'You are a helpful assistant'
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Test transformPrompts method
|
|
136
|
+
const transformedPathway = await pathwayManager.transformPrompts(legacyPathway);
|
|
137
|
+
|
|
138
|
+
// Verify the transformed pathway structure
|
|
139
|
+
t.truthy(transformedPathway);
|
|
140
|
+
t.true(Array.isArray(transformedPathway.prompt));
|
|
141
|
+
t.is(transformedPathway.prompt.length, 2);
|
|
142
|
+
|
|
143
|
+
// Verify prompts don't have file hashes
|
|
144
|
+
transformedPathway.prompt.forEach(prompt => {
|
|
145
|
+
t.falsy(prompt.fileHashes);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Verify no pathway-level file hashes
|
|
149
|
+
t.falsy(transformedPathway.fileHashes);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('pathwayManager removes duplicate file hashes at pathway level', async t => {
|
|
153
|
+
const pathwayManager = new PathwayManager(mockConfig, mockBasePathway);
|
|
154
|
+
|
|
155
|
+
// Replace storage with mock
|
|
156
|
+
pathwayManager.storage = new MockStorageStrategy();
|
|
157
|
+
|
|
158
|
+
// Test prompts with duplicate file hashes
|
|
159
|
+
const pathwayWithDuplicateFiles = {
|
|
160
|
+
prompt: [
|
|
161
|
+
{
|
|
162
|
+
name: 'First Task',
|
|
163
|
+
prompt: 'Analyze document 1',
|
|
164
|
+
files: ['abc123def456', 'def456ghi789']
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'Second Task',
|
|
168
|
+
prompt: 'Analyze document 2',
|
|
169
|
+
files: ['abc123def456', 'ghi789jkl012'] // abc123def456 is duplicate
|
|
170
|
+
}
|
|
171
|
+
],
|
|
172
|
+
systemPrompt: 'You are a helpful assistant'
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Test transformPrompts method
|
|
176
|
+
const transformedPathway = await pathwayManager.transformPrompts(pathwayWithDuplicateFiles);
|
|
177
|
+
|
|
178
|
+
// Verify pathway-level file hashes are deduplicated
|
|
179
|
+
t.truthy(transformedPathway.fileHashes);
|
|
180
|
+
t.deepEqual(transformedPathway.fileHashes, ['abc123def456', 'def456ghi789', 'ghi789jkl012']);
|
|
181
|
+
t.is(transformedPathway.fileHashes.length, 3); // Should not have duplicates
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('pathwayManager handles null and undefined files gracefully', async t => {
|
|
185
|
+
const pathwayManager = new PathwayManager(mockConfig, mockBasePathway);
|
|
186
|
+
|
|
187
|
+
// Replace storage with mock
|
|
188
|
+
pathwayManager.storage = new MockStorageStrategy();
|
|
189
|
+
|
|
190
|
+
// Test prompts with null/undefined files
|
|
191
|
+
const pathwayWithNullFiles = {
|
|
192
|
+
prompt: [
|
|
193
|
+
{
|
|
194
|
+
name: 'Task with null files',
|
|
195
|
+
prompt: 'Do something',
|
|
196
|
+
files: null
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: 'Task with undefined files',
|
|
200
|
+
prompt: 'Do something else'
|
|
201
|
+
// files property is undefined
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
systemPrompt: 'You are a helpful assistant'
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Test transformPrompts method
|
|
208
|
+
const transformedPathway = await pathwayManager.transformPrompts(pathwayWithNullFiles);
|
|
209
|
+
|
|
210
|
+
// Verify the transformation handles null/undefined gracefully
|
|
211
|
+
t.truthy(transformedPathway);
|
|
212
|
+
t.true(Array.isArray(transformedPathway.prompt));
|
|
213
|
+
|
|
214
|
+
// Both prompts should have empty or undefined fileHashes
|
|
215
|
+
transformedPathway.prompt.forEach(prompt => {
|
|
216
|
+
t.true(!prompt.fileHashes || prompt.fileHashes.length === 0);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// No pathway-level file hashes should be set
|
|
220
|
+
t.falsy(transformedPathway.fileHashes);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('pathwayManager _createPromptObject handles files correctly', t => {
|
|
224
|
+
const pathwayManager = new PathwayManager(mockConfig, mockBasePathway);
|
|
225
|
+
|
|
226
|
+
// Test with object prompt containing files
|
|
227
|
+
const promptWithFiles = {
|
|
228
|
+
name: 'Test Prompt',
|
|
229
|
+
prompt: 'Analyze this document',
|
|
230
|
+
files: ['file1hash', 'file2hash']
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const createdPrompt = pathwayManager._createPromptObject(promptWithFiles, 'System prompt');
|
|
234
|
+
|
|
235
|
+
t.is(createdPrompt.name, 'Test Prompt');
|
|
236
|
+
t.truthy(createdPrompt.fileHashes);
|
|
237
|
+
t.deepEqual(createdPrompt.fileHashes, ['file1hash', 'file2hash']);
|
|
238
|
+
|
|
239
|
+
// Test with string prompt (no files)
|
|
240
|
+
const stringPrompt = 'Simple text prompt';
|
|
241
|
+
const createdStringPrompt = pathwayManager._createPromptObject(stringPrompt, 'System prompt', 'Default Name');
|
|
242
|
+
|
|
243
|
+
t.is(createdStringPrompt.name, 'Default Name');
|
|
244
|
+
t.falsy(createdStringPrompt.fileHashes);
|
|
245
|
+
|
|
246
|
+
// Test with object prompt without files
|
|
247
|
+
const promptWithoutFiles = {
|
|
248
|
+
name: 'No Files Prompt',
|
|
249
|
+
prompt: 'Simple task'
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const createdPromptNoFiles = pathwayManager._createPromptObject(promptWithoutFiles, 'System prompt');
|
|
253
|
+
|
|
254
|
+
t.is(createdPromptNoFiles.name, 'No Files Prompt');
|
|
255
|
+
t.falsy(createdPromptNoFiles.fileHashes);
|
|
256
|
+
});
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cortex Pathway Argument Transformation Tests
|
|
3
|
+
*
|
|
4
|
+
* This test suite validates the argument transformation logic used when executing
|
|
5
|
+
* cortex pathways through the GraphQL executeWorkspace mutation. Specifically, it tests
|
|
6
|
+
* how the system transforms incoming pathway arguments (text, chatHistory, model) into
|
|
7
|
+
* the format expected by cortex pathways.
|
|
8
|
+
*
|
|
9
|
+
* Key transformation behaviors tested:
|
|
10
|
+
* - Merging text parameters with existing chatHistory entries
|
|
11
|
+
* - Preserving multimodal content (images, etc.) in the correct order
|
|
12
|
+
* - Creating new user messages when no chatHistory exists
|
|
13
|
+
* - Handling model selection with fallback to default values
|
|
14
|
+
* - Ensuring content arrays maintain proper JSON-stringified structure
|
|
15
|
+
*
|
|
16
|
+
* The transformation logic ensures that:
|
|
17
|
+
* 1. Text content is prepended to the last user message's content array
|
|
18
|
+
* 2. Existing multimodal content (e.g., images) is preserved
|
|
19
|
+
* 3. If no user message exists, a new one is created with the text
|
|
20
|
+
* 4. Model and systemPrompt are properly inherited from pathway configuration
|
|
21
|
+
*
|
|
22
|
+
* These tests simulate the core transformation logic from executePathwayWithFallback
|
|
23
|
+
* without requiring full integration test setup, allowing for focused unit testing
|
|
24
|
+
* of the argument transformation behavior.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import test from 'ava';
|
|
28
|
+
|
|
29
|
+
// Test the transformation logic directly without mocking
|
|
30
|
+
test('should format cortex pathway arguments correctly with existing chatHistory', (t) => {
|
|
31
|
+
// Mock the original prompt
|
|
32
|
+
const originalPrompt = {
|
|
33
|
+
name: 'summarize',
|
|
34
|
+
prompt: 'summarize this file',
|
|
35
|
+
cortexPathwayName: 'run_labeeb_agent'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Mock pathway data
|
|
39
|
+
const pathway = {
|
|
40
|
+
model: 'labeeb-agent',
|
|
41
|
+
systemPrompt: 'Test system prompt'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Mock incoming pathway args
|
|
45
|
+
const pathwayArgs = {
|
|
46
|
+
text: 'summarize the file',
|
|
47
|
+
chatHistory: [
|
|
48
|
+
{
|
|
49
|
+
role: 'user',
|
|
50
|
+
content: [
|
|
51
|
+
'{"type":"image_url","url":"test-url","image_url":{"url":"test-url"},"gcs":"test-gcs","originalFilename":"test.jpg","hash":"test-hash"}'
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Simulate the transformation logic from the executePathwayWithFallback function
|
|
58
|
+
const cortexArgs = {
|
|
59
|
+
model: pathway.model || pathwayArgs.model || "labeeb-agent",
|
|
60
|
+
chatHistory: [],
|
|
61
|
+
systemPrompt: pathway.systemPrompt
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// If we have existing chatHistory, use it as base
|
|
65
|
+
if (pathwayArgs.chatHistory && pathwayArgs.chatHistory.length > 0) {
|
|
66
|
+
cortexArgs.chatHistory = JSON.parse(JSON.stringify(pathwayArgs.chatHistory));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// If we have text parameter, we need to add it to the chatHistory
|
|
70
|
+
if (pathwayArgs.text) {
|
|
71
|
+
// Find the last user message or create a new one
|
|
72
|
+
let lastUserMessage = null;
|
|
73
|
+
for (let i = cortexArgs.chatHistory.length - 1; i >= 0; i--) {
|
|
74
|
+
if (cortexArgs.chatHistory[i].role === 'user') {
|
|
75
|
+
lastUserMessage = cortexArgs.chatHistory[i];
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (lastUserMessage) {
|
|
81
|
+
// Ensure content is an array
|
|
82
|
+
if (!Array.isArray(lastUserMessage.content)) {
|
|
83
|
+
lastUserMessage.content = [JSON.stringify({
|
|
84
|
+
type: "text",
|
|
85
|
+
text: lastUserMessage.content || ""
|
|
86
|
+
})];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Add the text parameter as a text content item
|
|
90
|
+
const textFromPrompt = originalPrompt?.prompt || pathwayArgs.text;
|
|
91
|
+
lastUserMessage.content.unshift(JSON.stringify({
|
|
92
|
+
type: "text",
|
|
93
|
+
text: `${pathwayArgs.text}\n\n${textFromPrompt}`
|
|
94
|
+
}));
|
|
95
|
+
} else {
|
|
96
|
+
// Create new user message with text
|
|
97
|
+
const textFromPrompt = originalPrompt?.prompt || pathwayArgs.text;
|
|
98
|
+
cortexArgs.chatHistory.push({
|
|
99
|
+
role: 'user',
|
|
100
|
+
content: [JSON.stringify({
|
|
101
|
+
type: "text",
|
|
102
|
+
text: `${pathwayArgs.text}\n\n${textFromPrompt}`
|
|
103
|
+
})]
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Verify the transformation
|
|
109
|
+
t.is(cortexArgs.model, 'labeeb-agent');
|
|
110
|
+
t.is(cortexArgs.systemPrompt, 'Test system prompt');
|
|
111
|
+
t.is(cortexArgs.chatHistory.length, 1);
|
|
112
|
+
|
|
113
|
+
// Check that the user message has the correct structure
|
|
114
|
+
const userMessage = cortexArgs.chatHistory[0];
|
|
115
|
+
t.is(userMessage.role, 'user');
|
|
116
|
+
t.true(Array.isArray(userMessage.content));
|
|
117
|
+
t.is(userMessage.content.length, 2);
|
|
118
|
+
|
|
119
|
+
// Check the text content was added first
|
|
120
|
+
const textContent = JSON.parse(userMessage.content[0]);
|
|
121
|
+
t.is(textContent.type, 'text');
|
|
122
|
+
t.is(textContent.text, 'summarize the file\n\nsummarize this file');
|
|
123
|
+
|
|
124
|
+
// Check the image content is preserved second
|
|
125
|
+
const imageContent = JSON.parse(userMessage.content[1]);
|
|
126
|
+
t.is(imageContent.type, 'image_url');
|
|
127
|
+
t.is(imageContent.gcs, 'test-gcs');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('should create new user message when no existing chatHistory', (t) => {
|
|
131
|
+
// Mock the original prompt
|
|
132
|
+
const originalPrompt = {
|
|
133
|
+
name: 'summarize',
|
|
134
|
+
prompt: 'summarize this file',
|
|
135
|
+
cortexPathwayName: 'run_labeeb_agent'
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Mock pathway data
|
|
139
|
+
const pathway = {
|
|
140
|
+
model: 'labeeb-agent',
|
|
141
|
+
systemPrompt: 'Test system prompt'
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Mock incoming pathway args with no chatHistory
|
|
145
|
+
const pathwayArgs = {
|
|
146
|
+
text: 'summarize the file'
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Simulate the transformation logic from the executePathwayWithFallback function
|
|
150
|
+
const cortexArgs = {
|
|
151
|
+
model: pathway.model || pathwayArgs.model || "labeeb-agent",
|
|
152
|
+
chatHistory: [],
|
|
153
|
+
systemPrompt: pathway.systemPrompt
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// If we have existing chatHistory, use it as base
|
|
157
|
+
if (pathwayArgs.chatHistory && pathwayArgs.chatHistory.length > 0) {
|
|
158
|
+
cortexArgs.chatHistory = JSON.parse(JSON.stringify(pathwayArgs.chatHistory));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If we have text parameter, we need to add it to the chatHistory
|
|
162
|
+
if (pathwayArgs.text) {
|
|
163
|
+
// Find the last user message or create a new one
|
|
164
|
+
let lastUserMessage = null;
|
|
165
|
+
for (let i = cortexArgs.chatHistory.length - 1; i >= 0; i--) {
|
|
166
|
+
if (cortexArgs.chatHistory[i].role === 'user') {
|
|
167
|
+
lastUserMessage = cortexArgs.chatHistory[i];
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (lastUserMessage) {
|
|
173
|
+
// Ensure content is an array
|
|
174
|
+
if (!Array.isArray(lastUserMessage.content)) {
|
|
175
|
+
lastUserMessage.content = [JSON.stringify({
|
|
176
|
+
type: "text",
|
|
177
|
+
text: lastUserMessage.content || ""
|
|
178
|
+
})];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Add the text parameter as a text content item
|
|
182
|
+
const textFromPrompt = originalPrompt?.prompt || pathwayArgs.text;
|
|
183
|
+
lastUserMessage.content.unshift(JSON.stringify({
|
|
184
|
+
type: "text",
|
|
185
|
+
text: `${pathwayArgs.text}\n\n${textFromPrompt}`
|
|
186
|
+
}));
|
|
187
|
+
} else {
|
|
188
|
+
// Create new user message with text
|
|
189
|
+
const textFromPrompt = originalPrompt?.prompt || pathwayArgs.text;
|
|
190
|
+
cortexArgs.chatHistory.push({
|
|
191
|
+
role: 'user',
|
|
192
|
+
content: [JSON.stringify({
|
|
193
|
+
type: "text",
|
|
194
|
+
text: `${pathwayArgs.text}\n\n${textFromPrompt}`
|
|
195
|
+
})]
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Verify the transformation
|
|
201
|
+
t.is(cortexArgs.model, 'labeeb-agent');
|
|
202
|
+
t.is(cortexArgs.systemPrompt, 'Test system prompt');
|
|
203
|
+
t.is(cortexArgs.chatHistory.length, 1);
|
|
204
|
+
|
|
205
|
+
// Check that a new user message was created
|
|
206
|
+
const userMessage = cortexArgs.chatHistory[0];
|
|
207
|
+
t.is(userMessage.role, 'user');
|
|
208
|
+
t.true(Array.isArray(userMessage.content));
|
|
209
|
+
t.is(userMessage.content.length, 1);
|
|
210
|
+
|
|
211
|
+
// Check the text content
|
|
212
|
+
const textContent = JSON.parse(userMessage.content[0]);
|
|
213
|
+
t.is(textContent.type, 'text');
|
|
214
|
+
t.is(textContent.text, 'summarize the file\n\nsummarize this file');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('should use default model when pathway model is not specified', (t) => {
|
|
218
|
+
// Mock the original prompt
|
|
219
|
+
const originalPrompt = {
|
|
220
|
+
name: 'summarize',
|
|
221
|
+
prompt: 'summarize this file',
|
|
222
|
+
cortexPathwayName: 'run_labeeb_agent'
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Mock pathway data without model
|
|
226
|
+
const pathway = {
|
|
227
|
+
systemPrompt: 'Test system prompt'
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Mock incoming pathway args
|
|
231
|
+
const pathwayArgs = {
|
|
232
|
+
text: 'summarize the file'
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Simulate the transformation logic
|
|
236
|
+
const cortexArgs = {
|
|
237
|
+
model: pathway.model || pathwayArgs.model || "labeeb-agent",
|
|
238
|
+
chatHistory: [],
|
|
239
|
+
systemPrompt: pathway.systemPrompt
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Verify default model is used
|
|
243
|
+
t.is(cortexArgs.model, 'labeeb-agent');
|
|
244
|
+
});
|
|
@@ -2,6 +2,9 @@ import test from 'ava';
|
|
|
2
2
|
import sinon from 'sinon';
|
|
3
3
|
import { getResolvers } from '../../../server/graphql.js';
|
|
4
4
|
|
|
5
|
+
// Mock callPathway to avoid actual external calls
|
|
6
|
+
const mockCallPathway = sinon.stub();
|
|
7
|
+
|
|
5
8
|
// Mock logger to avoid actual logging during tests
|
|
6
9
|
const mockLogger = {
|
|
7
10
|
info: sinon.stub(),
|
|
@@ -17,7 +20,7 @@ const mockConfig = {
|
|
|
17
20
|
|
|
18
21
|
test.beforeEach(t => {
|
|
19
22
|
// Reset stubs before each test
|
|
20
|
-
|
|
23
|
+
mockCallPathway.resetHistory();
|
|
21
24
|
mockLogger.info.resetHistory();
|
|
22
25
|
mockLogger.debug.resetHistory();
|
|
23
26
|
mockLogger.error.resetHistory();
|
|
@@ -168,3 +171,121 @@ test('executeWorkspace does not check format when promptNames not provided', asy
|
|
|
168
171
|
t.false(mockPathwayManager.isLegacyPromptFormat.called);
|
|
169
172
|
t.true(mockPathwayManager.getPathway.calledOnce);
|
|
170
173
|
});
|
|
174
|
+
|
|
175
|
+
test('executeWorkspace helper function DRY refactoring - structure verification', async t => {
|
|
176
|
+
// This test verifies that the DRY refactoring doesn't break existing functionality
|
|
177
|
+
// by testing that all three code paths (wildcard, specific prompts, default)
|
|
178
|
+
// still work with fallback to legacy execution
|
|
179
|
+
|
|
180
|
+
const mockRootResolver = sinon.stub().resolves({ result: 'legacy-result' });
|
|
181
|
+
|
|
182
|
+
// Test wildcard case with legacy fallback
|
|
183
|
+
const mockPathwayManager = {
|
|
184
|
+
getLatestPathways: sinon.stub().resolves({
|
|
185
|
+
'test-user': {
|
|
186
|
+
'test-pathway': {
|
|
187
|
+
prompt: [
|
|
188
|
+
{ name: 'prompt1' }, // No cortexPathwayName - will fallback
|
|
189
|
+
{ name: 'prompt2' } // No cortexPathwayName - will fallback
|
|
190
|
+
],
|
|
191
|
+
systemPrompt: 'Test system prompt'
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}),
|
|
195
|
+
isLegacyPromptFormat: sinon.stub().returns(false),
|
|
196
|
+
getPathways: sinon.stub().resolves([
|
|
197
|
+
{
|
|
198
|
+
name: 'prompt1',
|
|
199
|
+
systemPrompt: 'System prompt 1',
|
|
200
|
+
prompt: [{ messages: ['message1'] }],
|
|
201
|
+
fileHashes: [],
|
|
202
|
+
rootResolver: mockRootResolver
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'prompt2',
|
|
206
|
+
systemPrompt: 'System prompt 2',
|
|
207
|
+
prompt: [{ messages: ['message2'] }],
|
|
208
|
+
fileHashes: [],
|
|
209
|
+
rootResolver: mockRootResolver
|
|
210
|
+
}
|
|
211
|
+
]),
|
|
212
|
+
getResolvers: sinon.stub().returns({ Mutation: {} })
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const resolvers = getResolvers(mockConfig, {}, mockPathwayManager);
|
|
216
|
+
const executeWorkspaceResolver = resolvers.Query.executeWorkspace;
|
|
217
|
+
|
|
218
|
+
const mockContextValue = { config: mockConfig };
|
|
219
|
+
const mockInfo = {};
|
|
220
|
+
|
|
221
|
+
const args = {
|
|
222
|
+
userId: 'test-user',
|
|
223
|
+
pathwayName: 'test-pathway',
|
|
224
|
+
promptNames: ['*'], // Wildcard to execute all
|
|
225
|
+
text: 'test input'
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const result = await executeWorkspaceResolver(null, args, mockContextValue, mockInfo);
|
|
229
|
+
|
|
230
|
+
// Verify that legacy resolvers were called (indicating the DRY helper function worked)
|
|
231
|
+
t.is(mockRootResolver.callCount, 2); // Called twice for both prompts
|
|
232
|
+
|
|
233
|
+
// Verify result structure matches expected format
|
|
234
|
+
t.truthy(result);
|
|
235
|
+
t.truthy(result.result);
|
|
236
|
+
t.true(result.debug.includes('Executed 2 prompts in parallel'));
|
|
237
|
+
|
|
238
|
+
// Parse the result to verify both prompts were executed
|
|
239
|
+
const parsedResult = JSON.parse(result.result);
|
|
240
|
+
t.is(parsedResult.length, 2);
|
|
241
|
+
t.is(parsedResult[0].promptName, 'prompt1');
|
|
242
|
+
t.is(parsedResult[1].promptName, 'prompt2');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('executeWorkspace helper function DRY refactoring - default case structure', async t => {
|
|
246
|
+
// Test that the default case still works with the DRY helper function
|
|
247
|
+
|
|
248
|
+
const mockRootResolver = sinon.stub().resolves({ result: 'default-legacy-result' });
|
|
249
|
+
|
|
250
|
+
const mockPathwayManager = {
|
|
251
|
+
getLatestPathways: sinon.stub().resolves({
|
|
252
|
+
'test-user': {
|
|
253
|
+
'test-pathway': {
|
|
254
|
+
prompt: [
|
|
255
|
+
{ name: 'default-prompt' } // No cortexPathwayName
|
|
256
|
+
],
|
|
257
|
+
systemPrompt: 'Test system prompt'
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}),
|
|
261
|
+
getPathway: sinon.stub().resolves({
|
|
262
|
+
prompt: [{ name: 'default-prompt' }], // No cortexPathwayName
|
|
263
|
+
systemPrompt: 'Test system prompt',
|
|
264
|
+
fileHashes: [],
|
|
265
|
+
rootResolver: mockRootResolver
|
|
266
|
+
}),
|
|
267
|
+
getResolvers: sinon.stub().returns({ Mutation: {} })
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const resolvers = getResolvers(mockConfig, {}, mockPathwayManager);
|
|
271
|
+
const executeWorkspaceResolver = resolvers.Query.executeWorkspace;
|
|
272
|
+
|
|
273
|
+
const mockContextValue = { config: mockConfig };
|
|
274
|
+
const mockInfo = {};
|
|
275
|
+
|
|
276
|
+
const args = {
|
|
277
|
+
userId: 'test-user',
|
|
278
|
+
pathwayName: 'test-pathway',
|
|
279
|
+
text: 'test input'
|
|
280
|
+
// No promptNames provided - uses default case
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const result = await executeWorkspaceResolver(null, args, mockContextValue, mockInfo);
|
|
284
|
+
|
|
285
|
+
// Verify that legacy resolver was called (indicating DRY helper function worked for default case)
|
|
286
|
+
t.is(mockRootResolver.callCount, 1);
|
|
287
|
+
|
|
288
|
+
// Verify result structure
|
|
289
|
+
t.truthy(result);
|
|
290
|
+
t.is(result.result, 'default-legacy-result');
|
|
291
|
+
});
|