@aj-archipelago/cortex 1.3.67 → 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 (64) hide show
  1. package/config.js +27 -0
  2. package/helper-apps/cortex-doc-to-pdf/DocToPdfFunction/__init__.py +3 -0
  3. package/helper-apps/cortex-doc-to-pdf/DocToPdfFunction/function.json +20 -0
  4. package/helper-apps/cortex-doc-to-pdf/Dockerfile +46 -0
  5. package/helper-apps/cortex-doc-to-pdf/README.md +408 -0
  6. package/helper-apps/cortex-doc-to-pdf/converter.py +157 -0
  7. package/helper-apps/cortex-doc-to-pdf/docker-compose.yml +23 -0
  8. package/helper-apps/cortex-doc-to-pdf/document_converter.py +181 -0
  9. package/helper-apps/cortex-doc-to-pdf/examples/README.md +252 -0
  10. package/helper-apps/cortex-doc-to-pdf/examples/nodejs-client.js +266 -0
  11. package/helper-apps/cortex-doc-to-pdf/examples/package-lock.json +297 -0
  12. package/helper-apps/cortex-doc-to-pdf/examples/package.json +23 -0
  13. package/helper-apps/cortex-doc-to-pdf/function_app.py +85 -0
  14. package/helper-apps/cortex-doc-to-pdf/host.json +16 -0
  15. package/helper-apps/cortex-doc-to-pdf/request_handlers.py +193 -0
  16. package/helper-apps/cortex-doc-to-pdf/requirements.txt +3 -0
  17. package/helper-apps/cortex-doc-to-pdf/tests/run_tests.sh +26 -0
  18. package/helper-apps/cortex-doc-to-pdf/tests/test_conversion.py +320 -0
  19. package/helper-apps/cortex-doc-to-pdf/tests/test_streaming.py +419 -0
  20. package/helper-apps/cortex-file-handler/package-lock.json +1 -0
  21. package/helper-apps/cortex-file-handler/package.json +1 -0
  22. package/helper-apps/cortex-file-handler/src/services/ConversionService.js +81 -8
  23. package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +54 -7
  24. package/helper-apps/cortex-file-handler/tests/getOperations.test.js +19 -7
  25. package/lib/encodeCache.js +5 -0
  26. package/lib/keyValueStorageClient.js +5 -0
  27. package/lib/logger.js +1 -1
  28. package/lib/pathwayTools.js +8 -1
  29. package/lib/redisSubscription.js +6 -0
  30. package/lib/requestExecutor.js +4 -0
  31. package/lib/util.js +88 -0
  32. package/package.json +1 -1
  33. package/pathways/basePathway.js +3 -3
  34. package/pathways/bing_afagent.js +1 -0
  35. package/pathways/gemini_15_vision.js +1 -1
  36. package/pathways/google_cse.js +2 -2
  37. package/pathways/image_gemini_25.js +85 -0
  38. package/pathways/image_prompt_optimizer_gemini_25.js +149 -0
  39. package/pathways/image_qwen.js +28 -0
  40. package/pathways/image_seedream4.js +26 -0
  41. package/pathways/rag.js +1 -1
  42. package/pathways/rag_jarvis.js +1 -1
  43. package/pathways/system/entity/sys_entity_continue.js +1 -1
  44. package/pathways/system/entity/sys_generator_results.js +1 -1
  45. package/pathways/system/entity/tools/sys_tool_google_search.js +15 -2
  46. package/pathways/system/entity/tools/sys_tool_grok_x_search.js +3 -3
  47. package/pathways/system/entity/tools/sys_tool_image.js +28 -23
  48. package/pathways/system/entity/tools/sys_tool_image_gemini.js +135 -0
  49. package/server/graphql.js +9 -2
  50. package/server/modelExecutor.js +4 -0
  51. package/server/pathwayResolver.js +19 -18
  52. package/server/plugins/claude3VertexPlugin.js +13 -8
  53. package/server/plugins/gemini15ChatPlugin.js +15 -10
  54. package/server/plugins/gemini15VisionPlugin.js +2 -23
  55. package/server/plugins/gemini25ImagePlugin.js +155 -0
  56. package/server/plugins/modelPlugin.js +3 -2
  57. package/server/plugins/openAiChatPlugin.js +6 -6
  58. package/server/plugins/replicateApiPlugin.js +268 -12
  59. package/server/plugins/veoVideoPlugin.js +15 -1
  60. package/server/rest.js +2 -0
  61. package/server/typeDef.js +96 -10
  62. package/tests/integration/apptekTranslatePlugin.integration.test.js +1 -1
  63. package/tests/unit/core/pathwayManager.test.js +2 -4
  64. package/tests/unit/plugins/gemini25ImagePlugin.test.js +294 -0
package/server/typeDef.js CHANGED
@@ -1,30 +1,113 @@
1
+ // Check if a value is a JSON Schema object for parameter typing
2
+ const isJsonSchemaObject = (value) => {
3
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
4
+ // Basic JSON Schema indicators
5
+ return (
6
+ typeof value.type === 'string' ||
7
+ value.$ref !== undefined ||
8
+ value.oneOf !== undefined ||
9
+ value.anyOf !== undefined ||
10
+ value.allOf !== undefined ||
11
+ value.enum !== undefined ||
12
+ value.properties !== undefined ||
13
+ value.items !== undefined
14
+ );
15
+ };
16
+
17
+ // Extract the default value from a JSON Schema object or return the value as-is
18
+ const extractValueFromTypeSpec = (value) => {
19
+ if (isJsonSchemaObject(value)) {
20
+ return value.hasOwnProperty('default') ? value.default : undefined;
21
+ }
22
+ return value;
23
+ };
24
+
25
+ // Process parameters to convert any type specification objects to their actual values
26
+ const processPathwayParameters = (params) => {
27
+ if (!params || typeof params !== 'object') {
28
+ return params;
29
+ }
30
+
31
+ const processed = {};
32
+ for (const [key, value] of Object.entries(params)) {
33
+ processed[key] = extractValueFromTypeSpec(value);
34
+ }
35
+ return processed;
36
+ };
37
+
1
38
  const getGraphQlType = (value) => {
39
+ // The value might be an object with JSON Schema type specification
40
+ if (isJsonSchemaObject(value)) {
41
+ const schema = value;
42
+ // Map JSON Schema to GraphQL
43
+ if (schema.type === 'boolean') {
44
+ return { type: 'Boolean', defaultValue: schema.default === undefined ? undefined : schema.default };
45
+ }
46
+ if (schema.type === 'string') {
47
+ return { type: 'String', defaultValue: schema.default === undefined ? undefined : `"${schema.default}"` };
48
+ }
49
+ if (schema.type === 'integer') {
50
+ return { type: 'Int', defaultValue: schema.default };
51
+ }
52
+ if (schema.type === 'number') {
53
+ const def = schema.default;
54
+ return { type: 'Float', defaultValue: def };
55
+ }
56
+ if (schema.type === 'array') {
57
+ // Support arrays of primitive types; fall back to JSON string for complex types
58
+ const items = schema.items || {};
59
+ const def = schema.default;
60
+ const defaultArray = Array.isArray(def) ? JSON.stringify(def) : '[]';
61
+ if (items.type === 'string') {
62
+ return { type: '[String]', defaultValue: defaultArray };
63
+ }
64
+ if (items.type === 'integer') {
65
+ return { type: '[Int]', defaultValue: defaultArray };
66
+ }
67
+ if (items.type === 'number') {
68
+ return { type: '[Float]', defaultValue: defaultArray };
69
+ }
70
+ if (items.type === 'boolean') {
71
+ return { type: '[Boolean]', defaultValue: defaultArray };
72
+ }
73
+ // Unknown item type: pass as serialized JSON string argument
74
+ return { type: 'String', defaultValue: def === undefined ? '"[]"' : `"${JSON.stringify(def).replace(/"/g, '\\"')}"` };
75
+ }
76
+ if (schema.type === 'object' || schema.properties) {
77
+ // Until explicit input types are defined, accept as stringified JSON
78
+ const def = schema.default;
79
+ return { type: 'String', defaultValue: def === undefined ? '"{}"' : `"${JSON.stringify(def).replace(/"/g, '\\"')}"` };
80
+ }
81
+ }
82
+
83
+ // Otherwise, autodetect the type
2
84
  switch (typeof value) {
3
85
  case 'boolean':
4
- return {type: 'Boolean'};
86
+ return {type: 'Boolean', defaultValue: value};
5
87
  case 'string':
6
- return {type: 'String'};
88
+ return {type: 'String', defaultValue: `"${value}"`};
7
89
  case 'number':
8
- return {type: 'Int'};
90
+ // Check if it's an integer or float
91
+ return Number.isInteger(value) ? {type: 'Int', defaultValue: value} : {type: 'Float', defaultValue: value};
9
92
  case 'object':
10
93
  if (Array.isArray(value)) {
11
94
  if (value.length > 0 && typeof(value[0]) === 'string') {
12
- return {type: '[String]'};
95
+ return {type: '[String]', defaultValue: JSON.stringify(value)};
13
96
  }
14
97
  else {
15
- // New case for MultiMessage type
98
+ // Check if it's MultiMessage (content is array) or Message (content is string)
16
99
  if (Array.isArray(value[0]?.content)) {
17
- return {type: '[MultiMessage]'};
100
+ return {type: '[MultiMessage]', defaultValue: `"${JSON.stringify(value).replace(/"/g, '\\"')}"`};
18
101
  }
19
102
  else {
20
- return {type: '[Message]'};
103
+ return {type: '[Message]', defaultValue: `"${JSON.stringify(value).replace(/"/g, '\\"')}"`};
21
104
  }
22
105
  }
23
106
  } else {
24
- return {type: `[${value.objName}]`};
107
+ return {type: `[${value.objName}]`, defaultValue: JSON.stringify(value)};
25
108
  }
26
109
  default:
27
- return {type: 'String'};
110
+ return {type: 'String', defaultValue: `"${value}"`};
28
111
  }
29
112
  };
30
113
 
@@ -80,7 +163,7 @@ const getPathwayTypeDefAndExtendQuery = (pathway) => {
80
163
  };
81
164
  });
82
165
 
83
- const gqlDefinition = `${type}\n\n${responseType}\n\nextend type Query {${name}(${paramsStr}): ${objName}}`;
166
+ const gqlDefinition = `${type}\n\n${responseType}\n\nextend type Query {${name}${paramsStr ? `(${paramsStr})` : ''}: ${objName}}`;
84
167
 
85
168
  return {
86
169
  gqlDefinition,
@@ -100,4 +183,7 @@ export {
100
183
  getMessageTypeDefs,
101
184
  getPathwayTypeDef,
102
185
  userPathwayInputParameters,
186
+ isJsonSchemaObject,
187
+ extractValueFromTypeSpec,
188
+ processPathwayParameters,
103
189
  };
@@ -155,7 +155,7 @@ test.serial('AppTek Plugin: Force failure and test GPT-4 Omni fallback', async (
155
155
  });
156
156
 
157
157
  // Test AppTek failure with default fallback (translate_groq)
158
- test.skip('AppTek Plugin: Force failure and test default fallback', async (t) => {
158
+ test('AppTek Plugin: Force failure and test default fallback', async (t) => {
159
159
  // Set a longer timeout for this test since Groq might be slower
160
160
  t.timeout(180000); // 3 minutes
161
161
 
@@ -193,8 +193,7 @@ test('_createPromptObject handles empty system prompt', t => {
193
193
 
194
194
  t.true(result instanceof Prompt);
195
195
  t.is(result.name, 'test_prompt');
196
- t.is(result.messages[0].content, '');
197
- t.is(result.messages[1].content, '{{text}}\n\nTest prompt text');
196
+ t.is(result.messages[0].content, '{{text}}\n\nTest prompt text');
198
197
  });
199
198
 
200
199
  test('_createPromptObject handles null system prompt', t => {
@@ -206,8 +205,7 @@ test('_createPromptObject handles null system prompt', t => {
206
205
 
207
206
  t.true(result instanceof Prompt);
208
207
  t.is(result.name, 'test_prompt');
209
- t.is(result.messages[0].content, '');
210
- t.is(result.messages[1].content, '{{text}}\n\nTest prompt text');
208
+ t.is(result.messages[0].content, '{{text}}\n\nTest prompt text');
211
209
  });
212
210
 
213
211
  test('putPathway requires userId and secret', async t => {
@@ -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
+ });