@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
@@ -232,20 +232,32 @@ test.serial("should fetch remote file", async (t) => {
232
232
  const requestId = uuidv4();
233
233
  const remoteUrl = "https://example.com/test.txt";
234
234
 
235
+ // Mock external HEAD and GET to avoid network dependency
236
+ const body = "hello from example";
237
+ const scope = nock("https://example.com")
238
+ .head("/test.txt")
239
+ .reply(200, "", {
240
+ "Content-Type": "text/plain",
241
+ "Content-Length": body.length.toString(),
242
+ })
243
+ .get("/test.txt")
244
+ .reply(200, body, {
245
+ "Content-Type": "text/plain",
246
+ "Content-Length": body.length.toString(),
247
+ });
248
+
235
249
  const response = await axios.get(baseUrl, {
236
250
  params: {
237
251
  fetch: remoteUrl,
238
252
  requestId,
239
253
  },
240
254
  validateStatus: (status) => true,
255
+ timeout: 10000,
241
256
  });
242
257
 
243
- t.is(response.status, 400, "Should reject invalid URL");
244
- t.is(
245
- response.data,
246
- "Invalid or inaccessible URL",
247
- "Should return correct error message",
248
- );
258
+ t.is(response.status, 200, "Should fetch and store remote file");
259
+ t.truthy(response.data.url, "Should return file URL");
260
+ t.true(scope.isDone(), "All external requests should be mocked and used");
249
261
  });
250
262
 
251
263
  // Test: Redis caching behavior for remote files
@@ -296,7 +308,7 @@ test.serial("should cache remote files in Redis", async (t) => {
296
308
 
297
309
  t.is(secondResponse.status, 200, "Should return cached file from Redis");
298
310
  t.truthy(secondResponse.data.url, "Should return cached file URL");
299
-
311
+
300
312
  // Cleanup
301
313
  await cleanupHashAndFile(hash, secondResponse.data.url, baseUrl);
302
314
  } finally {
@@ -9,6 +9,11 @@ class EncodeCache {
9
9
  }
10
10
 
11
11
  encode(value) {
12
+ // Handle CortexResponse objects by extracting the text content
13
+ if (value && typeof value === 'object' && value.constructor && value.constructor.name === 'CortexResponse') {
14
+ value = value.output_text || '';
15
+ }
16
+
12
17
  if (this.encodeCache.get(value) !== -1) {
13
18
  return this.encodeCache.get(value);
14
19
  }
@@ -30,6 +30,11 @@ const keyValueStorageClient = new Keyv(storageConnectionString, {
30
30
  namespace: `${cortexId}-cortex-context`
31
31
  });
32
32
 
33
+ // Handle Redis connection errors to prevent crashes
34
+ keyValueStorageClient.on('error', (error) => {
35
+ logger.error(`Keyv Redis connection error: ${error}`);
36
+ });
37
+
33
38
  // Set values to keyv
34
39
  async function setv(key, value) {
35
40
  return keyValueStorageClient && (await keyValueStorageClient.set(key, value));
package/lib/logger.js CHANGED
@@ -37,9 +37,9 @@ const getTransport = () => {
37
37
  case 'production':
38
38
  return new winston.transports.Console({ level: 'info', format: winston.format.combine(suppressNonErrorFormat(), prodFormat) });
39
39
  case 'development':
40
- case 'test':
41
40
  return new winston.transports.Console({ level: 'verbose', format: winston.format.combine(suppressNonErrorFormat(), debugFormat) });
42
41
  case 'debug':
42
+ case 'test':
43
43
  return new winston.transports.Console({ level: 'debug', format: winston.format.combine(suppressNonErrorFormat(), debugFormat) });
44
44
  default:
45
45
  // Default to development settings if NODE_ENV is not set or unknown
@@ -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 prompt)
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
- return new Prompt({
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 {
@@ -5,6 +5,7 @@ import { publishRequestProgress } from "../lib/redisSubscription.js";
5
5
  import { getSemanticChunks } from "../server/chunker.js";
6
6
  import logger from '../lib/logger.js';
7
7
  import { requestState } from '../server/requestState.js';
8
+ import { processPathwayParameters } from '../server/typeDef.js';
8
9
 
9
10
  // callPathway - call a pathway from another pathway
10
11
  const callPathway = async (pathwayName, inArgs, pathwayResolver) => {
@@ -17,12 +18,18 @@ const callPathway = async (pathwayName, inArgs, pathwayResolver) => {
17
18
  throw new Error(`Pathway ${pathwayName} not found`);
18
19
  }
19
20
 
21
+ // Merge pathway default parameters with input args, similar to GraphQL typeDef behavior
22
+ const mergedParams = { ...pathway.defaultInputParameters, ...pathway.inputParameters, ...args };
23
+
24
+ // Process the merged parameters to convert type specification objects to actual values
25
+ const processedArgs = processPathwayParameters(mergedParams);
26
+
20
27
  const parent = {};
21
28
  let rootRequestId = pathwayResolver?.rootRequestId || pathwayResolver?.requestId;
22
29
 
23
30
  const contextValue = { config, pathway, requestState };
24
31
 
25
- let data = await pathway.rootResolver(parent, {...args, rootRequestId}, contextValue );
32
+ let data = await pathway.rootResolver(parent, {...processedArgs, rootRequestId}, contextValue );
26
33
 
27
34
  if (pathwayResolver && contextValue.pathwayResolver) {
28
35
  pathwayResolver.mergeResolver(contextValue.pathwayResolver);
@@ -24,6 +24,12 @@ if (connectionString) {
24
24
  logger.info(`Using Redis publish for channel(s) ${requestProgressChannel}, ${requestProgressSubscriptionsChannel}`);
25
25
  try {
26
26
  publisherClient = connectionString && new Redis(connectionString);
27
+ // Handle Redis publisher client errors to prevent crashes
28
+ if (publisherClient) {
29
+ publisherClient.on('error', (error) => {
30
+ logger.error(`Redis publisherClient error: ${error}`);
31
+ });
32
+ }
27
33
  } catch (error) {
28
34
  logger.error(`Redis connection error: ${error}`);
29
35
  }
@@ -20,6 +20,10 @@ let client;
20
20
  if (connectionString) {
21
21
  try {
22
22
  client = new Redis(connectionString);
23
+ // Handle Redis connection errors to prevent crashes
24
+ client.on('error', (error) => {
25
+ logger.error(`Redis client connection error: ${error}`);
26
+ });
23
27
  } catch (error) {
24
28
  logger.error(`Redis connection error: ${error}`);
25
29
  }
package/lib/util.js CHANGED
@@ -10,6 +10,8 @@ import { promisify } from 'util';
10
10
  import { axios } from './requestExecutor.js';
11
11
  import { config } from '../config.js';
12
12
  import fs from 'fs';
13
+ import path from 'path';
14
+ import FormData from 'form-data';
13
15
 
14
16
  const pipeline = promisify(stream.pipeline);
15
17
  const MEDIA_API_URL = config.get('whisperMediaApiUrl');
@@ -412,6 +414,146 @@ function getAvailableFiles(chatHistory) {
412
414
  return availableFiles;
413
415
  }
414
416
 
417
+ // Helper function to upload base64 image data to cloud storage
418
+ const uploadImageToCloud = async (base64Data, mimeType, pathwayResolver = null) => {
419
+ let tempFilePath = null;
420
+ let tempDir = null;
421
+
422
+ try {
423
+ // Convert base64 to buffer
424
+ const imageBuffer = Buffer.from(base64Data, 'base64');
425
+
426
+ // Determine file extension from mime type
427
+ const extension = mimeType.split('/')[1] || 'png';
428
+ const filename = `generated_image_${Date.now()}.${extension}`;
429
+
430
+ // Create temporary file
431
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'image-upload-'));
432
+ tempFilePath = path.join(tempDir, filename);
433
+
434
+ // Write buffer to temp file
435
+ fs.writeFileSync(tempFilePath, imageBuffer);
436
+
437
+ // Upload to file handler service
438
+ const fileHandlerUrl = MEDIA_API_URL;
439
+ if (!fileHandlerUrl) {
440
+ throw new Error('WHISPER_MEDIA_API_URL is not set');
441
+ }
442
+ const requestId = uuidv4();
443
+
444
+ // Create form data for upload
445
+ const formData = new FormData();
446
+ formData.append('file', fs.createReadStream(tempFilePath), {
447
+ filename: filename,
448
+ contentType: mimeType
449
+ });
450
+
451
+ // Upload file
452
+ const uploadResponse = await axios.post(`${fileHandlerUrl}?requestId=${requestId}`, formData, {
453
+ headers: {
454
+ ...formData.getHeaders()
455
+ },
456
+ timeout: 30000
457
+ });
458
+
459
+ if (uploadResponse.data && uploadResponse.data.url) {
460
+ return uploadResponse.data.url;
461
+ } else {
462
+ throw new Error('No URL returned from file handler');
463
+ }
464
+
465
+ } catch (error) {
466
+ const errorMessage = `Failed to upload image: ${error.message}`;
467
+ if (pathwayResolver && pathwayResolver.logError) {
468
+ pathwayResolver.logError(errorMessage);
469
+ } else {
470
+ logger.error(errorMessage);
471
+ }
472
+ throw error;
473
+ } finally {
474
+ // Clean up temp files
475
+ if (tempFilePath && fs.existsSync(tempFilePath)) {
476
+ try {
477
+ fs.unlinkSync(tempFilePath);
478
+ } catch (cleanupError) {
479
+ const warningMessage = `Failed to clean up temp file: ${cleanupError.message}`;
480
+ if (pathwayResolver && pathwayResolver.logWarning) {
481
+ pathwayResolver.logWarning(warningMessage);
482
+ } else {
483
+ logger.warn(warningMessage);
484
+ }
485
+ }
486
+ }
487
+ if (tempDir && fs.existsSync(tempDir)) {
488
+ try {
489
+ fs.rmdirSync(tempDir);
490
+ } catch (cleanupError) {
491
+ const warningMessage = `Failed to clean up temp directory: ${cleanupError.message}`;
492
+ if (pathwayResolver && pathwayResolver.logWarning) {
493
+ pathwayResolver.logWarning(warningMessage);
494
+ } else {
495
+ logger.warn(warningMessage);
496
+ }
497
+ }
498
+ }
499
+ }
500
+ };
501
+
502
+ /**
503
+ * Convert file hashes to content format suitable for LLM processing
504
+ * @param {Array<string>} fileHashes - Array of file hashes to resolve
505
+ * @param {Object} config - Configuration object with file service endpoints
506
+ * @returns {Promise<Array<string>>} Array of stringified file content objects
507
+ */
508
+ async function resolveFileHashesToContent(fileHashes, config) {
509
+ if (!fileHashes || fileHashes.length === 0) return [];
510
+
511
+ const fileContentPromises = fileHashes.map(async (hash) => {
512
+ try {
513
+ // Use the existing file handler (cortex-file-handler) to resolve file hashes
514
+ const fileHandlerUrl = config?.get?.('whisperMediaApiUrl');
515
+
516
+ if (fileHandlerUrl && fileHandlerUrl !== 'null') {
517
+ // Make request to file handler to get file content by hash
518
+ const response = await axios.get(fileHandlerUrl, {
519
+ params: { hash: hash, checkHash: true }
520
+ });
521
+ if (response.status === 200) {
522
+ const fileData = response.data;
523
+ const fileUrl = fileData.shortLivedUrl || fileData.url;
524
+ const convertedUrl = fileData.converted?.url;
525
+ const convertedGcsUrl = fileData.converted?.gcs;
526
+
527
+ return JSON.stringify({
528
+ type: "image_url",
529
+ url: convertedUrl,
530
+ image_url: { url: convertedUrl },
531
+ gcs: convertedGcsUrl || fileData.gcs, // Add GCS URL for Gemini models
532
+ originalFilename: fileData.filename,
533
+ hash: hash
534
+ });
535
+ }
536
+ }
537
+
538
+ // Fallback: create a placeholder that indicates file resolution is needed
539
+ return JSON.stringify({
540
+ type: "file_hash",
541
+ hash: hash,
542
+ _cortex_needs_resolution: true
543
+ });
544
+ } catch (error) {
545
+ // Return error indicator
546
+ return JSON.stringify({
547
+ type: "file_error",
548
+ hash: hash,
549
+ error: error.message
550
+ });
551
+ }
552
+ });
553
+
554
+ return Promise.all(fileContentPromises);
555
+ }
556
+
415
557
  export {
416
558
  getUniqueId,
417
559
  getSearchResultId,
@@ -421,10 +563,12 @@ export {
421
563
  chatArgsHasType,
422
564
  deleteTempPath,
423
565
  downloadFile,
566
+ uploadImageToCloud,
424
567
  convertSrtToText,
425
568
  alignSubtitles,
426
569
  getMediaChunks,
427
570
  markCompletedForCleanUp,
428
571
  removeOldImageAndFileContent,
429
- getAvailableFiles
572
+ getAvailableFiles,
573
+ resolveFileHashesToContent
430
574
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aj-archipelago/cortex",
3
- "version": "1.3.66",
3
+ "version": "1.4.0",
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": {
@@ -3,11 +3,11 @@ import { typeDef } from '../server/typeDef.js';
3
3
 
4
4
  // all default definitions of a single pathway
5
5
  export default {
6
- prompt: `{{text}}`,
6
+ prompt: '{{text}}',
7
7
  defaultInputParameters: {
8
- text: ``,
8
+ text: '',
9
9
  async: false, // switch to enable async mode
10
- contextId: ``, // used to identify the context of the request,
10
+ contextId: '', // used to identify the context of the request,
11
11
  stream: false, // switch to enable stream mode
12
12
  },
13
13
  inputParameters: {},
@@ -5,6 +5,7 @@ import { config } from '../config.js';
5
5
  import logger from '../lib/logger.js';
6
6
 
7
7
  export default {
8
+ prompt: ["{{text}}"],
8
9
  inputParameters: {
9
10
  text: ``,
10
11
  tool_choice: 'auto',
@@ -10,7 +10,7 @@ export default {
10
10
  ],
11
11
  inputParameters: {
12
12
  chatHistory: [{role: '', content: []}],
13
- contextId: ``,
13
+ contextId: '',
14
14
  },
15
15
  max_tokens: 2048,
16
16
  model: 'gemini-pro-15-vision',
@@ -3,8 +3,8 @@
3
3
 
4
4
  export default {
5
5
  inputParameters: {
6
- text: ``,
7
- q: ``,
6
+ text: '',
7
+ q: '',
8
8
  num: 10,
9
9
  start: 1,
10
10
  safe: 'off', // 'off' | 'active'
@@ -0,0 +1,85 @@
1
+ import { Prompt } from '../server/prompt.js';
2
+ import { callPathway } from '../lib/pathwayTools.js';
3
+ import logger from '../lib/logger.js';
4
+
5
+ export default {
6
+ prompt: [],
7
+ executePathway: async ({args, runAllPrompts, resolver}) => {
8
+ let finalPrompt = args.text || '';
9
+
10
+ const { optimizePrompt, input_image, input_image_2, input_image_3 } = { ...resolver.pathway.inputParameters, ...args };
11
+
12
+ // Check if prompt optimization is enabled
13
+ if (optimizePrompt && optimizePrompt !== false && finalPrompt) {
14
+ try {
15
+ // Call the prompt optimizer pathway
16
+ const optimizerResult = await callPathway('image_prompt_optimizer_gemini_25', {
17
+ userPrompt: finalPrompt,
18
+ hasInputImages: !!input_image || !!input_image_2 || !!input_image_3
19
+ }, resolver);
20
+
21
+ if (optimizerResult) {
22
+ finalPrompt = optimizerResult;
23
+ }
24
+ } catch (error) {
25
+ logger.warn(`Prompt optimization failed, proceeding with original prompt: ${error.message}`);
26
+ }
27
+ }
28
+
29
+ // Build the user content with text and images
30
+ const userContent = [{"type": "text", "text": finalPrompt}];
31
+
32
+ // Add input images if provided
33
+ if (input_image) {
34
+ userContent.push({
35
+ "type": "image_url",
36
+ "image_url": {"url": input_image}
37
+ });
38
+ }
39
+ if (input_image_2) {
40
+ userContent.push({
41
+ "type": "image_url",
42
+ "image_url": {"url": input_image_2}
43
+ });
44
+ }
45
+ if (input_image_3) {
46
+ userContent.push({
47
+ "type": "image_url",
48
+ "image_url": {"url": input_image_3}
49
+ });
50
+ }
51
+
52
+ const userMessage = {"role": "user", "content": userContent};
53
+
54
+ const systemMessage = {"role": "system", "content": "Instructions:\nYou are Jarvis Vision 2.5, an AI entity working for a prestigious international news agency. Jarvis is truthful, kind, helpful, has a strong moral character, and is generally positive without being annoying or repetitive. Your primary expertise is both image analysis and image generation/editing. You are capable of:\n\n1. Understanding and interpreting complex image data, identifying patterns and trends\n2. Generating new images based on detailed descriptions\n3. Editing existing images according to specific instructions\n4. Delivering insights and results in a clear, digestible format\n\nYou know the current date and time - it is {{now}}. When generating or editing images, ensure they are appropriate for professional news media use and follow ethical guidelines."};
55
+
56
+ const promptMessages = [systemMessage, userMessage];
57
+
58
+ resolver.pathwayPrompt = [
59
+ new Prompt({ messages: promptMessages }),
60
+ ];
61
+
62
+ return await runAllPrompts({ ...args });
63
+ },
64
+
65
+ inputParameters: {
66
+ text: "",
67
+ input_image: "", // URL to first input image
68
+ input_image_2: "", // URL to second input image
69
+ input_image_3: "", // URL to third input image
70
+ contextId: ``,
71
+ response_modalities: ["TEXT", "IMAGE"],
72
+ optimizePrompt: false, // Enable prompt optimization using Google's best practices
73
+ },
74
+ max_tokens: 32000,
75
+ model: 'gemini-25-flash-image',
76
+ useInputChunking: false,
77
+ enableDuplicateRequests: false,
78
+ timeout: 600,
79
+ geminiSafetySettings: [
80
+ {category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_ONLY_HIGH'},
81
+ {category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold: 'BLOCK_ONLY_HIGH'},
82
+ {category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_ONLY_HIGH'},
83
+ {category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_ONLY_HIGH'}
84
+ ],
85
+ }