@aj-archipelago/cortex 1.3.66 → 1.4.0

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.
Files changed (84) hide show
  1. package/config.js +27 -0
  2. package/helper-apps/cortex-autogen2/Dockerfile +88 -21
  3. package/helper-apps/cortex-autogen2/docker-compose.yml +15 -8
  4. package/helper-apps/cortex-autogen2/host.json +5 -0
  5. package/helper-apps/cortex-autogen2/pyproject.toml +82 -25
  6. package/helper-apps/cortex-autogen2/requirements.txt +84 -14
  7. package/helper-apps/cortex-autogen2/services/redis_publisher.py +129 -3
  8. package/helper-apps/cortex-autogen2/task_processor.py +432 -116
  9. package/helper-apps/cortex-autogen2/tools/__init__.py +2 -0
  10. package/helper-apps/cortex-autogen2/tools/azure_blob_tools.py +32 -0
  11. package/helper-apps/cortex-autogen2/tools/azure_foundry_agents.py +50 -14
  12. package/helper-apps/cortex-autogen2/tools/file_tools.py +169 -44
  13. package/helper-apps/cortex-autogen2/tools/google_cse.py +117 -0
  14. package/helper-apps/cortex-autogen2/tools/search_tools.py +655 -98
  15. package/helper-apps/cortex-doc-to-pdf/DocToPdfFunction/__init__.py +3 -0
  16. package/helper-apps/cortex-doc-to-pdf/DocToPdfFunction/function.json +20 -0
  17. package/helper-apps/cortex-doc-to-pdf/Dockerfile +46 -0
  18. package/helper-apps/cortex-doc-to-pdf/README.md +408 -0
  19. package/helper-apps/cortex-doc-to-pdf/converter.py +157 -0
  20. package/helper-apps/cortex-doc-to-pdf/docker-compose.yml +23 -0
  21. package/helper-apps/cortex-doc-to-pdf/document_converter.py +181 -0
  22. package/helper-apps/cortex-doc-to-pdf/examples/README.md +252 -0
  23. package/helper-apps/cortex-doc-to-pdf/examples/nodejs-client.js +266 -0
  24. package/helper-apps/cortex-doc-to-pdf/examples/package-lock.json +297 -0
  25. package/helper-apps/cortex-doc-to-pdf/examples/package.json +23 -0
  26. package/helper-apps/cortex-doc-to-pdf/function_app.py +85 -0
  27. package/helper-apps/cortex-doc-to-pdf/host.json +16 -0
  28. package/helper-apps/cortex-doc-to-pdf/request_handlers.py +193 -0
  29. package/helper-apps/cortex-doc-to-pdf/requirements.txt +3 -0
  30. package/helper-apps/cortex-doc-to-pdf/tests/run_tests.sh +26 -0
  31. package/helper-apps/cortex-doc-to-pdf/tests/test_conversion.py +320 -0
  32. package/helper-apps/cortex-doc-to-pdf/tests/test_streaming.py +419 -0
  33. package/helper-apps/cortex-file-handler/package-lock.json +1 -0
  34. package/helper-apps/cortex-file-handler/package.json +1 -0
  35. package/helper-apps/cortex-file-handler/src/services/ConversionService.js +81 -8
  36. package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +54 -7
  37. package/helper-apps/cortex-file-handler/tests/getOperations.test.js +19 -7
  38. package/lib/encodeCache.js +5 -0
  39. package/lib/keyValueStorageClient.js +5 -0
  40. package/lib/logger.js +1 -1
  41. package/lib/pathwayManager.js +42 -8
  42. package/lib/pathwayTools.js +8 -1
  43. package/lib/redisSubscription.js +6 -0
  44. package/lib/requestExecutor.js +4 -0
  45. package/lib/util.js +145 -1
  46. package/package.json +1 -1
  47. package/pathways/basePathway.js +3 -3
  48. package/pathways/bing_afagent.js +1 -0
  49. package/pathways/gemini_15_vision.js +1 -1
  50. package/pathways/google_cse.js +2 -2
  51. package/pathways/image_gemini_25.js +85 -0
  52. package/pathways/image_prompt_optimizer_gemini_25.js +149 -0
  53. package/pathways/image_qwen.js +28 -0
  54. package/pathways/image_seedream4.js +26 -0
  55. package/pathways/rag.js +1 -1
  56. package/pathways/rag_jarvis.js +1 -1
  57. package/pathways/system/entity/sys_entity_continue.js +1 -1
  58. package/pathways/system/entity/sys_generator_results.js +1 -1
  59. package/pathways/system/entity/tools/sys_tool_google_search.js +15 -2
  60. package/pathways/system/entity/tools/sys_tool_grok_x_search.js +3 -3
  61. package/pathways/system/entity/tools/sys_tool_image.js +28 -23
  62. package/pathways/system/entity/tools/sys_tool_image_gemini.js +135 -0
  63. package/pathways/system/workspaces/run_workspace_prompt.js +0 -3
  64. package/server/executeWorkspace.js +381 -0
  65. package/server/graphql.js +14 -182
  66. package/server/modelExecutor.js +4 -0
  67. package/server/pathwayResolver.js +19 -18
  68. package/server/plugins/claude3VertexPlugin.js +13 -8
  69. package/server/plugins/gemini15ChatPlugin.js +15 -10
  70. package/server/plugins/gemini15VisionPlugin.js +2 -23
  71. package/server/plugins/gemini25ImagePlugin.js +155 -0
  72. package/server/plugins/modelPlugin.js +3 -2
  73. package/server/plugins/openAiChatPlugin.js +6 -6
  74. package/server/plugins/replicateApiPlugin.js +268 -12
  75. package/server/plugins/veoVideoPlugin.js +15 -1
  76. package/server/rest.js +2 -0
  77. package/server/typeDef.js +96 -10
  78. package/tests/integration/apptekTranslatePlugin.integration.test.js +1 -1
  79. package/tests/unit/core/parser.test.js +0 -1
  80. package/tests/unit/core/pathwayManager.test.js +2 -4
  81. package/tests/unit/core/pathwayManagerWithFiles.test.js +256 -0
  82. package/tests/unit/graphql_executeWorkspace_transformation.test.js +244 -0
  83. package/tests/unit/plugins/gemini25ImagePlugin.test.js +294 -0
  84. package/tests/unit/server/graphql.test.js +122 -1
@@ -0,0 +1,294 @@
1
+ import test from 'ava';
2
+ import Gemini25ImagePlugin from '../../../server/plugins/gemini25ImagePlugin.js';
3
+ import { PathwayResolver } from '../../../server/pathwayResolver.js';
4
+ import { config } from '../../../config.js';
5
+ import { requestState } from '../../../server/requestState.js';
6
+
7
+ // Mock logger to prevent issues in tests
8
+ const mockLogger = {
9
+ debug: () => {},
10
+ info: () => {},
11
+ warn: () => {},
12
+ error: () => {}
13
+ };
14
+
15
+ // Mock the logger module globally
16
+ global.logger = mockLogger;
17
+
18
+ function createResolverWithPlugin(pluginClass, modelName = 'test-model') {
19
+ const pathway = {
20
+ name: 'test-pathway',
21
+ model: modelName,
22
+ prompt: 'test prompt',
23
+ toolCallback: () => {} // Mock tool callback
24
+ };
25
+
26
+ const model = {
27
+ name: modelName,
28
+ type: 'GEMINI-2.5-IMAGE'
29
+ };
30
+
31
+ const resolver = new PathwayResolver({
32
+ config,
33
+ pathway,
34
+ args: {},
35
+ endpoints: { [modelName]: model }
36
+ });
37
+
38
+ resolver.modelExecutor.plugin = new pluginClass(pathway, model);
39
+ return resolver;
40
+ }
41
+
42
+ test('Gemini25ImagePlugin - filters undefined tool calls from buffer', async t => {
43
+ const resolver = createResolverWithPlugin(Gemini25ImagePlugin);
44
+ const plugin = resolver.modelExecutor.plugin;
45
+
46
+ // Simulate buffer with undefined elements
47
+ plugin.toolCallsBuffer = [
48
+ undefined,
49
+ {
50
+ id: 'call_1_1234567890',
51
+ type: 'function',
52
+ function: {
53
+ name: 'test_tool',
54
+ arguments: '{"param": "value"}'
55
+ }
56
+ }
57
+ ];
58
+
59
+ // Mock the tool callback
60
+ let capturedToolCalls = null;
61
+ plugin.pathwayToolCallback = (args, message, resolver) => {
62
+ capturedToolCalls = message.tool_calls;
63
+ };
64
+
65
+ // Mock requestProgress and pathwayResolver
66
+ const requestProgress = { progress: 0, started: true };
67
+ const pathwayResolver = { args: {} };
68
+
69
+ // Mock requestState
70
+ requestState[plugin.requestId] = { pathwayResolver };
71
+
72
+ // Simulate a tool_calls finish reason
73
+ const eventData = {
74
+ candidates: [{
75
+ finishReason: 'STOP'
76
+ }]
77
+ };
78
+
79
+ // Set hadToolCalls to true to trigger tool_calls finish reason
80
+ plugin.hadToolCalls = true;
81
+
82
+ // Process the stream event
83
+ plugin.processStreamEvent({ data: JSON.stringify(eventData) }, requestProgress);
84
+
85
+ // Verify that undefined elements were filtered out
86
+ t.truthy(capturedToolCalls, 'Tool callback should have been called');
87
+ t.is(capturedToolCalls.length, 1, 'Should have filtered out undefined elements');
88
+ t.is(capturedToolCalls[0].function.name, 'test_tool', 'Valid tool call should be preserved');
89
+
90
+ // Clean up
91
+ delete requestState[plugin.requestId];
92
+ });
93
+
94
+ test('Gemini25ImagePlugin - handles empty buffer gracefully', async t => {
95
+ const resolver = createResolverWithPlugin(Gemini25ImagePlugin);
96
+ const plugin = resolver.modelExecutor.plugin;
97
+
98
+ // Empty buffer
99
+ plugin.toolCallsBuffer = [];
100
+
101
+ // Mock the tool callback
102
+ let callbackCalled = false;
103
+ plugin.pathwayToolCallback = () => {
104
+ callbackCalled = true;
105
+ };
106
+
107
+ // Mock requestProgress and pathwayResolver
108
+ const requestProgress = { progress: 0, started: true };
109
+ const pathwayResolver = { args: {} };
110
+
111
+ // Mock requestState
112
+ requestState[plugin.requestId] = { pathwayResolver };
113
+
114
+ // Simulate a tool_calls finish reason
115
+ const eventData = {
116
+ candidates: [{
117
+ finishReason: 'STOP'
118
+ }]
119
+ };
120
+
121
+ // Set hadToolCalls to true
122
+ plugin.hadToolCalls = true;
123
+
124
+ // Process the stream event
125
+ plugin.processStreamEvent({ data: JSON.stringify(eventData) }, requestProgress);
126
+
127
+ // Verify that callback was not called with empty buffer
128
+ t.falsy(callbackCalled, 'Tool callback should not be called with empty buffer');
129
+
130
+ // Clean up
131
+ delete requestState[plugin.requestId];
132
+ });
133
+
134
+ test('Gemini25ImagePlugin - handles image artifacts in streaming', async t => {
135
+ const resolver = createResolverWithPlugin(Gemini25ImagePlugin);
136
+ const plugin = resolver.modelExecutor.plugin;
137
+
138
+ // Mock requestProgress
139
+ const requestProgress = { progress: 0, started: true };
140
+
141
+ // Simulate event with image artifact
142
+ const eventData = {
143
+ candidates: [{
144
+ content: {
145
+ parts: [{
146
+ inlineData: {
147
+ data: 'base64imagedata',
148
+ mimeType: 'image/png'
149
+ }
150
+ }]
151
+ }
152
+ }]
153
+ };
154
+
155
+ // Process the stream event
156
+ plugin.processStreamEvent({ data: JSON.stringify(eventData) }, requestProgress);
157
+
158
+ // Verify that image artifacts were captured
159
+ t.truthy(requestProgress.artifacts, 'Artifacts should be created');
160
+ t.is(requestProgress.artifacts.length, 1, 'Should have one image artifact');
161
+ t.is(requestProgress.artifacts[0].type, 'image', 'Artifact should be of type image');
162
+ t.is(requestProgress.artifacts[0].data, 'base64imagedata', 'Image data should be preserved');
163
+ t.is(requestProgress.artifacts[0].mimeType, 'image/png', 'MIME type should be preserved');
164
+ });
165
+
166
+ test('Gemini25ImagePlugin - handles multiple image artifacts', async t => {
167
+ const resolver = createResolverWithPlugin(Gemini25ImagePlugin);
168
+ const plugin = resolver.modelExecutor.plugin;
169
+
170
+ // Mock requestProgress
171
+ const requestProgress = { progress: 0, started: true };
172
+
173
+ // Simulate event with multiple image artifacts
174
+ const eventData = {
175
+ candidates: [{
176
+ content: {
177
+ parts: [
178
+ {
179
+ inlineData: {
180
+ data: 'base64image1',
181
+ mimeType: 'image/png'
182
+ }
183
+ },
184
+ {
185
+ inlineData: {
186
+ data: 'base64image2',
187
+ mimeType: 'image/jpeg'
188
+ }
189
+ }
190
+ ]
191
+ }
192
+ }]
193
+ };
194
+
195
+ // Process the stream event
196
+ plugin.processStreamEvent({ data: JSON.stringify(eventData) }, requestProgress);
197
+
198
+ // Verify that multiple image artifacts were captured
199
+ t.truthy(requestProgress.artifacts, 'Artifacts should be created');
200
+ t.is(requestProgress.artifacts.length, 2, 'Should have two image artifacts');
201
+ t.is(requestProgress.artifacts[0].type, 'image', 'First artifact should be of type image');
202
+ t.is(requestProgress.artifacts[0].data, 'base64image1', 'First image data should be preserved');
203
+ t.is(requestProgress.artifacts[0].mimeType, 'image/png', 'First MIME type should be preserved');
204
+ t.is(requestProgress.artifacts[1].type, 'image', 'Second artifact should be of type image');
205
+ t.is(requestProgress.artifacts[1].data, 'base64image2', 'Second image data should be preserved');
206
+ t.is(requestProgress.artifacts[1].mimeType, 'image/jpeg', 'Second MIME type should be preserved');
207
+ });
208
+
209
+ test('Gemini25ImagePlugin - handles mixed content with text and images', async t => {
210
+ const resolver = createResolverWithPlugin(Gemini25ImagePlugin);
211
+ const plugin = resolver.modelExecutor.plugin;
212
+
213
+ // Mock requestProgress
214
+ const requestProgress = { progress: 0, started: true };
215
+
216
+ // Simulate event with mixed content (text + image)
217
+ const eventData = {
218
+ candidates: [{
219
+ content: {
220
+ parts: [
221
+ {
222
+ text: 'Here is an image:'
223
+ },
224
+ {
225
+ inlineData: {
226
+ data: 'base64imagedata',
227
+ mimeType: 'image/png'
228
+ }
229
+ }
230
+ ]
231
+ }
232
+ }]
233
+ };
234
+
235
+ // Process the stream event
236
+ plugin.processStreamEvent({ data: JSON.stringify(eventData) }, requestProgress);
237
+
238
+ // Verify that both text content and image artifacts were handled
239
+ t.truthy(requestProgress.artifacts, 'Artifacts should be created');
240
+ t.is(requestProgress.artifacts.length, 1, 'Should have one image artifact');
241
+ t.is(requestProgress.artifacts[0].type, 'image', 'Artifact should be of type image');
242
+ t.is(requestProgress.artifacts[0].data, 'base64imagedata', 'Image data should be preserved');
243
+
244
+ // Clean up
245
+ delete requestState[plugin.requestId];
246
+ });
247
+
248
+ test('Gemini25ImagePlugin - handles response_modalities parameter', async t => {
249
+ const resolver = createResolverWithPlugin(Gemini25ImagePlugin);
250
+ const plugin = resolver.modelExecutor.plugin;
251
+
252
+ // Test with response_modalities parameter
253
+ const parameters = {
254
+ response_modalities: '["TEXT", "IMAGE"]'
255
+ };
256
+
257
+ const prompt = {
258
+ prompt: 'test prompt'
259
+ };
260
+
261
+ const cortexRequest = {
262
+ pathway: {}
263
+ };
264
+
265
+ const requestParams = plugin.getRequestParameters('test text', parameters, prompt, cortexRequest);
266
+
267
+ // Verify that response_modalities was added to generationConfig
268
+ t.truthy(requestParams.generationConfig.response_modalities, 'response_modalities should be set');
269
+ t.deepEqual(requestParams.generationConfig.response_modalities, ['TEXT', 'IMAGE'], 'response_modalities should be parsed correctly');
270
+ });
271
+
272
+ test('Gemini25ImagePlugin - handles response_modalities from pathway', async t => {
273
+ const resolver = createResolverWithPlugin(Gemini25ImagePlugin);
274
+ const plugin = resolver.modelExecutor.plugin;
275
+
276
+ // Test with response_modalities from pathway
277
+ const parameters = {};
278
+
279
+ const prompt = {
280
+ prompt: 'test prompt'
281
+ };
282
+
283
+ const cortexRequest = {
284
+ pathway: {
285
+ response_modalities: ['TEXT', 'IMAGE']
286
+ }
287
+ };
288
+
289
+ const requestParams = plugin.getRequestParameters('test text', parameters, prompt, cortexRequest);
290
+
291
+ // Verify that response_modalities was added to generationConfig
292
+ t.truthy(requestParams.generationConfig.response_modalities, 'response_modalities should be set');
293
+ t.deepEqual(requestParams.generationConfig.response_modalities, ['TEXT', 'IMAGE'], 'response_modalities should be set correctly');
294
+ });
@@ -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
- sinon.restore();
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
+ });