@aj-archipelago/cortex 1.4.22 → 1.4.24

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 (30) hide show
  1. package/FILE_SYSTEM_DOCUMENTATION.md +116 -48
  2. package/config.js +9 -0
  3. package/lib/fileUtils.js +329 -214
  4. package/package.json +1 -1
  5. package/pathways/system/entity/files/sys_read_file_collection.js +22 -11
  6. package/pathways/system/entity/files/sys_update_file_metadata.js +18 -8
  7. package/pathways/system/entity/sys_entity_agent.js +8 -6
  8. package/pathways/system/entity/tools/sys_tool_codingagent.js +4 -4
  9. package/pathways/system/entity/tools/sys_tool_editfile.js +35 -24
  10. package/pathways/system/entity/tools/sys_tool_file_collection.js +93 -36
  11. package/pathways/system/entity/tools/sys_tool_image.js +1 -1
  12. package/pathways/system/entity/tools/sys_tool_image_gemini.js +1 -1
  13. package/pathways/system/entity/tools/sys_tool_readfile.js +4 -4
  14. package/pathways/system/entity/tools/sys_tool_slides_gemini.js +1 -1
  15. package/pathways/system/entity/tools/sys_tool_video_veo.js +1 -1
  16. package/pathways/system/entity/tools/sys_tool_view_image.js +10 -5
  17. package/pathways/system/workspaces/run_workspace_agent.js +4 -1
  18. package/pathways/video_seedance.js +2 -0
  19. package/server/executeWorkspace.js +45 -2
  20. package/server/pathwayResolver.js +18 -0
  21. package/server/plugins/replicateApiPlugin.js +18 -0
  22. package/server/typeDef.js +10 -1
  23. package/tests/integration/features/tools/fileCollection.test.js +254 -248
  24. package/tests/integration/features/tools/fileOperations.test.js +131 -81
  25. package/tests/integration/graphql/async/stream/vendors/claude_streaming.test.js +3 -4
  26. package/tests/integration/graphql/async/stream/vendors/gemini_streaming.test.js +3 -4
  27. package/tests/integration/graphql/async/stream/vendors/grok_streaming.test.js +3 -4
  28. package/tests/integration/graphql/async/stream/vendors/openai_streaming.test.js +5 -5
  29. package/tests/unit/core/fileCollection.test.js +86 -25
  30. package/pathways/system/workspaces/run_workspace_research_agent.js +0 -27
@@ -21,8 +21,8 @@ test.after.always('cleanup', async () => {
21
21
  test('Claude vendor streaming over subscriptions emits OAI-style deltas', async (t) => {
22
22
  const response = await testServer.executeOperation({
23
23
  query: `
24
- query($text: String!, $chatHistory: [MultiMessage]!, $stream: Boolean, $aiStyle: String) {
25
- sys_entity_agent(text: $text, chatHistory: $chatHistory, stream: $stream, aiStyle: $aiStyle) {
24
+ query($text: String!, $chatHistory: [MultiMessage]!, $stream: Boolean) {
25
+ sys_entity_agent(text: $text, chatHistory: $chatHistory, stream: $stream) {
26
26
  result
27
27
  }
28
28
  }
@@ -30,8 +30,7 @@ test('Claude vendor streaming over subscriptions emits OAI-style deltas', async
30
30
  variables: {
31
31
  text: 'Say hi',
32
32
  chatHistory: [{ role: 'user', content: ['Say hi'] }],
33
- stream: true,
34
- aiStyle: 'Anthropic'
33
+ stream: true
35
34
  }
36
35
  });
37
36
 
@@ -21,8 +21,8 @@ test.after.always('cleanup', async () => {
21
21
  test('Gemini vendor streaming over subscriptions emits OAI-style deltas', async (t) => {
22
22
  const response = await testServer.executeOperation({
23
23
  query: `
24
- query($text: String!, $chatHistory: [MultiMessage]!, $stream: Boolean, $aiStyle: String) {
25
- sys_entity_agent(text: $text, chatHistory: $chatHistory, stream: $stream, aiStyle: $aiStyle) {
24
+ query($text: String!, $chatHistory: [MultiMessage]!, $stream: Boolean) {
25
+ sys_entity_agent(text: $text, chatHistory: $chatHistory, stream: $stream) {
26
26
  result
27
27
  }
28
28
  }
@@ -30,8 +30,7 @@ test('Gemini vendor streaming over subscriptions emits OAI-style deltas', async
30
30
  variables: {
31
31
  text: 'Say hi',
32
32
  chatHistory: [{ role: 'user', content: ['Say hi'] }],
33
- stream: true,
34
- aiStyle: 'Google'
33
+ stream: true
35
34
  }
36
35
  });
37
36
 
@@ -21,8 +21,8 @@ test.after.always('cleanup', async () => {
21
21
  test('XAI Grok vendor streaming over subscriptions emits OAI-style deltas', async (t) => {
22
22
  const response = await testServer.executeOperation({
23
23
  query: `
24
- query($text: String!, $chatHistory: [MultiMessage]!, $stream: Boolean, $aiStyle: String) {
25
- sys_entity_agent(text: $text, chatHistory: $chatHistory, stream: $stream, aiStyle: $aiStyle) {
24
+ query($text: String!, $chatHistory: [MultiMessage]!, $stream: Boolean) {
25
+ sys_entity_agent(text: $text, chatHistory: $chatHistory, stream: $stream) {
26
26
  result
27
27
  }
28
28
  }
@@ -30,8 +30,7 @@ test('XAI Grok vendor streaming over subscriptions emits OAI-style deltas', asyn
30
30
  variables: {
31
31
  text: 'Say hi',
32
32
  chatHistory: [{ role: 'user', content: ['Say hi'] }],
33
- stream: true,
34
- aiStyle: 'XAI'
33
+ stream: true
35
34
  }
36
35
  });
37
36
 
@@ -21,8 +21,8 @@ test.after.always('cleanup', async () => {
21
21
  test('OpenAI vendor streaming over subscriptions emits OAI-style deltas', async (t) => {
22
22
  const response = await testServer.executeOperation({
23
23
  query: `
24
- query($text: String!, $chatHistory: [MultiMessage]!, $stream: Boolean, $aiStyle: String) {
25
- sys_entity_agent(text: $text, chatHistory: $chatHistory, stream: $stream, aiStyle: $aiStyle) {
24
+ query($text: String!, $chatHistory: [MultiMessage]!, $stream: Boolean) {
25
+ sys_entity_agent(text: $text, chatHistory: $chatHistory, stream: $stream) {
26
26
  result
27
27
  }
28
28
  }
@@ -30,8 +30,7 @@ test('OpenAI vendor streaming over subscriptions emits OAI-style deltas', async
30
30
  variables: {
31
31
  text: 'Say hi',
32
32
  chatHistory: [{ role: 'user', content: ['Say hi'] }],
33
- stream: true,
34
- aiStyle: 'OpenAI'
33
+ stream: true
35
34
  }
36
35
  });
37
36
 
@@ -63,7 +62,8 @@ test('OpenAI vendor streaming over subscriptions emits OAI-style deltas', async
63
62
  .filter(Boolean);
64
63
 
65
64
  if (models.length > 0) {
66
- t.truthy(models.find(m => /gpt-5-chat/.test(m)));
65
+ // Model could be gpt-4.1, gpt-4, gpt-5, etc. depending on config
66
+ t.truthy(models.find(m => /gpt-4|gpt-5/.test(m)));
67
67
  }
68
68
  });
69
69
 
@@ -500,42 +500,103 @@ test('addFileToCollection should preserve original displayFilename for converted
500
500
  }
501
501
  });
502
502
 
503
- test('syncFilesToCollection should determine MIME type from URL, not displayFilename', async t => {
504
- const { syncFilesToCollection } = await import('../../../lib/fileUtils.js');
503
+ // Note: Tests that require Redis (adding files to collection) are in integration tests
504
+ // These unit tests only test behavior that doesn't require Redis
505
+
506
+ test('syncAndStripFilesFromChatHistory should leave all files when no contextId', async t => {
507
+ const { syncAndStripFilesFromChatHistory } = await import('../../../lib/fileUtils.js');
505
508
 
506
- // Simulate chat history with converted file
507
- const contextId = `test-sync-converted-${Date.now()}`;
508
509
  const chatHistory = [
509
510
  {
510
511
  role: 'user',
511
512
  content: [
512
513
  {
513
- type: 'file',
514
- url: 'https://example.com/converted-report.md', // Converted to markdown
515
- gcs: 'gs://bucket/converted-report.md',
516
- hash: 'hash123'
514
+ type: 'image_url',
515
+ image_url: { url: 'https://example.com/image.jpg' },
516
+ hash: 'somehash'
517
517
  }
518
518
  ]
519
519
  }
520
520
  ];
521
521
 
522
- try {
523
- await syncFilesToCollection(chatHistory, contextId, null);
524
-
525
- const { loadFileCollection } = await import('../../../lib/fileUtils.js');
526
- const collection = await loadFileCollection(contextId, null, false);
527
- t.is(collection.length, 1);
528
-
529
- // MIME type should be from URL (.md), not from any displayFilename
530
- t.is(collection[0].mimeType, 'text/markdown', 'MIME type should be determined from URL');
531
- t.is(collection[0].url, 'https://example.com/converted-report.md');
532
- } finally {
533
- // Cleanup
534
- const { getRedisClient } = await import('../../../lib/fileUtils.js');
535
- const redisClient = await getRedisClient();
536
- if (redisClient) {
537
- await redisClient.del(`FileStoreMap:ctx:${contextId}`);
522
+ // No contextId - should leave files in place
523
+ const { chatHistory: processedHistory } = await syncAndStripFilesFromChatHistory(chatHistory, null, null);
524
+
525
+ t.is(processedHistory[0].content[0].type, 'image_url');
526
+ t.is(processedHistory[0].content[0].image_url.url, 'https://example.com/image.jpg');
527
+ });
528
+
529
+ test('syncAndStripFilesFromChatHistory should leave files when collection is empty', async t => {
530
+ const { syncAndStripFilesFromChatHistory } = await import('../../../lib/fileUtils.js');
531
+
532
+ // Use a unique contextId that won't have any files
533
+ const contextId = `test-empty-${Date.now()}`;
534
+
535
+ const chatHistory = [
536
+ {
537
+ role: 'user',
538
+ content: [
539
+ {
540
+ type: 'image_url',
541
+ image_url: { url: 'https://example.com/image.jpg' },
542
+ hash: 'somehash'
543
+ }
544
+ ]
538
545
  }
539
- }
546
+ ];
547
+
548
+ // Empty collection - files should stay in place (not stripped)
549
+ const { chatHistory: processedHistory } = await syncAndStripFilesFromChatHistory(chatHistory, contextId, null);
550
+
551
+ t.is(processedHistory[0].content[0].type, 'image_url');
552
+ t.is(processedHistory[0].content[0].image_url.url, 'https://example.com/image.jpg');
553
+ });
554
+
555
+ test('syncAndStripFilesFromChatHistory should handle empty chat history', async t => {
556
+ const { syncAndStripFilesFromChatHistory } = await import('../../../lib/fileUtils.js');
557
+
558
+ const { chatHistory: result1 } = await syncAndStripFilesFromChatHistory([], 'context', null);
559
+ t.deepEqual(result1, []);
560
+
561
+ const { chatHistory: result2 } = await syncAndStripFilesFromChatHistory(null, 'context', null);
562
+ t.deepEqual(result2, []);
540
563
  });
541
564
 
565
+ test('syncAndStripFilesFromChatHistory should preserve non-file content', async t => {
566
+ const { syncAndStripFilesFromChatHistory } = await import('../../../lib/fileUtils.js');
567
+
568
+ const contextId = `test-preserve-${Date.now()}`;
569
+
570
+ const chatHistory = [
571
+ {
572
+ role: 'user',
573
+ content: [
574
+ { type: 'text', text: 'Hello world' },
575
+ {
576
+ type: 'image_url',
577
+ image_url: { url: 'https://example.com/image.jpg' },
578
+ hash: 'somehash'
579
+ }
580
+ ]
581
+ },
582
+ {
583
+ role: 'assistant',
584
+ content: 'I see an image'
585
+ }
586
+ ];
587
+
588
+ const { chatHistory: processedHistory } = await syncAndStripFilesFromChatHistory(chatHistory, contextId, null);
589
+
590
+ // Text content should be preserved
591
+ t.is(processedHistory[0].content[0].type, 'text');
592
+ t.is(processedHistory[0].content[0].text, 'Hello world');
593
+
594
+ // Image not in collection should be preserved
595
+ t.is(processedHistory[0].content[1].type, 'image_url');
596
+
597
+ // Assistant message should be preserved
598
+ t.is(processedHistory[1].role, 'assistant');
599
+ t.is(processedHistory[1].content, 'I see an image');
600
+ });
601
+
602
+
@@ -1,27 +0,0 @@
1
- import { callPathway } from '../../../lib/pathwayTools.js';
2
-
3
- export default {
4
- // The main prompt function that takes the input text and asks to generate a summary.
5
- prompt: [],
6
-
7
- inputParameters: {
8
- model: "oai-gpt41",
9
- aiStyle: "OpenAI",
10
- chatHistory: [{role: '', content: []}],
11
- },
12
- timeout: 600,
13
-
14
- executePathway: async ({args, _runAllPrompts, resolver}) => {
15
- // chatHistory is always passed in complete
16
- const response = await callPathway('sys_entity_agent', {
17
- ...args,
18
- chatHistory: args.chatHistory || [],
19
- stream: false,
20
- useMemory: false,
21
- researchMode: true
22
- }, resolver);
23
-
24
- return response;
25
- }
26
- }
27
-