@aj-archipelago/cortex 1.3.62 → 1.3.63

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 (211) hide show
  1. package/.github/workflows/cortex-file-handler-test.yml +61 -0
  2. package/README.md +31 -7
  3. package/config/default.example.json +15 -0
  4. package/config.js +133 -12
  5. package/helper-apps/cortex-autogen2/DigiCertGlobalRootCA.crt.pem +22 -0
  6. package/helper-apps/cortex-autogen2/Dockerfile +31 -0
  7. package/helper-apps/cortex-autogen2/Dockerfile.worker +41 -0
  8. package/helper-apps/cortex-autogen2/README.md +183 -0
  9. package/helper-apps/cortex-autogen2/__init__.py +1 -0
  10. package/helper-apps/cortex-autogen2/agents.py +131 -0
  11. package/helper-apps/cortex-autogen2/docker-compose.yml +20 -0
  12. package/helper-apps/cortex-autogen2/function_app.py +55 -0
  13. package/helper-apps/cortex-autogen2/host.json +15 -0
  14. package/helper-apps/cortex-autogen2/main.py +126 -0
  15. package/helper-apps/cortex-autogen2/poetry.lock +3652 -0
  16. package/helper-apps/cortex-autogen2/pyproject.toml +36 -0
  17. package/helper-apps/cortex-autogen2/requirements.txt +20 -0
  18. package/helper-apps/cortex-autogen2/send_task.py +105 -0
  19. package/helper-apps/cortex-autogen2/services/__init__.py +1 -0
  20. package/helper-apps/cortex-autogen2/services/azure_queue.py +85 -0
  21. package/helper-apps/cortex-autogen2/services/redis_publisher.py +153 -0
  22. package/helper-apps/cortex-autogen2/task_processor.py +488 -0
  23. package/helper-apps/cortex-autogen2/tools/__init__.py +24 -0
  24. package/helper-apps/cortex-autogen2/tools/azure_blob_tools.py +175 -0
  25. package/helper-apps/cortex-autogen2/tools/azure_foundry_agents.py +601 -0
  26. package/helper-apps/cortex-autogen2/tools/coding_tools.py +72 -0
  27. package/helper-apps/cortex-autogen2/tools/download_tools.py +48 -0
  28. package/helper-apps/cortex-autogen2/tools/file_tools.py +545 -0
  29. package/helper-apps/cortex-autogen2/tools/search_tools.py +646 -0
  30. package/helper-apps/cortex-azure-cleaner/README.md +36 -0
  31. package/helper-apps/cortex-file-converter/README.md +93 -0
  32. package/helper-apps/cortex-file-converter/key_to_pdf.py +104 -0
  33. package/helper-apps/cortex-file-converter/list_blob_extensions.py +89 -0
  34. package/helper-apps/cortex-file-converter/process_azure_keynotes.py +181 -0
  35. package/helper-apps/cortex-file-converter/requirements.txt +1 -0
  36. package/helper-apps/cortex-file-handler/.env.test.azure.ci +7 -0
  37. package/helper-apps/cortex-file-handler/.env.test.azure.sample +1 -1
  38. package/helper-apps/cortex-file-handler/.env.test.gcs.ci +10 -0
  39. package/helper-apps/cortex-file-handler/.env.test.gcs.sample +2 -2
  40. package/helper-apps/cortex-file-handler/INTERFACE.md +41 -0
  41. package/helper-apps/cortex-file-handler/package.json +1 -1
  42. package/helper-apps/cortex-file-handler/scripts/setup-azure-container.js +41 -17
  43. package/helper-apps/cortex-file-handler/scripts/setup-test-containers.js +30 -15
  44. package/helper-apps/cortex-file-handler/scripts/test-azure.sh +32 -6
  45. package/helper-apps/cortex-file-handler/scripts/test-gcs.sh +24 -2
  46. package/helper-apps/cortex-file-handler/scripts/validate-env.js +128 -0
  47. package/helper-apps/cortex-file-handler/src/blobHandler.js +161 -51
  48. package/helper-apps/cortex-file-handler/src/constants.js +3 -0
  49. package/helper-apps/cortex-file-handler/src/fileChunker.js +10 -8
  50. package/helper-apps/cortex-file-handler/src/index.js +116 -9
  51. package/helper-apps/cortex-file-handler/src/redis.js +61 -1
  52. package/helper-apps/cortex-file-handler/src/services/ConversionService.js +11 -8
  53. package/helper-apps/cortex-file-handler/src/services/FileConversionService.js +2 -2
  54. package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +88 -6
  55. package/helper-apps/cortex-file-handler/src/services/storage/GCSStorageProvider.js +58 -0
  56. package/helper-apps/cortex-file-handler/src/services/storage/StorageFactory.js +25 -5
  57. package/helper-apps/cortex-file-handler/src/services/storage/StorageProvider.js +9 -0
  58. package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +120 -16
  59. package/helper-apps/cortex-file-handler/src/start.js +27 -17
  60. package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +52 -1
  61. package/helper-apps/cortex-file-handler/tests/blobHandler.test.js +40 -0
  62. package/helper-apps/cortex-file-handler/tests/checkHashShortLived.test.js +553 -0
  63. package/helper-apps/cortex-file-handler/tests/cleanup.test.js +46 -52
  64. package/helper-apps/cortex-file-handler/tests/containerConversionFlow.test.js +451 -0
  65. package/helper-apps/cortex-file-handler/tests/containerNameParsing.test.js +229 -0
  66. package/helper-apps/cortex-file-handler/tests/containerParameterFlow.test.js +392 -0
  67. package/helper-apps/cortex-file-handler/tests/conversionResilience.test.js +7 -2
  68. package/helper-apps/cortex-file-handler/tests/deleteOperations.test.js +348 -0
  69. package/helper-apps/cortex-file-handler/tests/fileChunker.test.js +23 -2
  70. package/helper-apps/cortex-file-handler/tests/fileUpload.test.js +11 -5
  71. package/helper-apps/cortex-file-handler/tests/getOperations.test.js +58 -24
  72. package/helper-apps/cortex-file-handler/tests/postOperations.test.js +11 -4
  73. package/helper-apps/cortex-file-handler/tests/shortLivedUrlConversion.test.js +225 -0
  74. package/helper-apps/cortex-file-handler/tests/start.test.js +8 -12
  75. package/helper-apps/cortex-file-handler/tests/storage/StorageFactory.test.js +80 -0
  76. package/helper-apps/cortex-file-handler/tests/storage/StorageService.test.js +388 -22
  77. package/helper-apps/cortex-file-handler/tests/testUtils.helper.js +74 -0
  78. package/lib/cortexResponse.js +153 -0
  79. package/lib/entityConstants.js +21 -3
  80. package/lib/logger.js +21 -4
  81. package/lib/pathwayTools.js +28 -9
  82. package/lib/util.js +49 -0
  83. package/package.json +1 -1
  84. package/pathways/basePathway.js +1 -0
  85. package/pathways/bing_afagent.js +54 -1
  86. package/pathways/call_tools.js +2 -3
  87. package/pathways/chat_jarvis.js +1 -1
  88. package/pathways/google_cse.js +27 -0
  89. package/pathways/grok_live_search.js +18 -0
  90. package/pathways/system/entity/memory/sys_memory_lookup_required.js +1 -0
  91. package/pathways/system/entity/memory/sys_memory_required.js +1 -0
  92. package/pathways/system/entity/memory/sys_search_memory.js +1 -0
  93. package/pathways/system/entity/sys_entity_agent.js +56 -4
  94. package/pathways/system/entity/sys_generator_quick.js +1 -0
  95. package/pathways/system/entity/tools/sys_tool_bing_search_afagent.js +26 -0
  96. package/pathways/system/entity/tools/sys_tool_google_search.js +141 -0
  97. package/pathways/system/entity/tools/sys_tool_grok_x_search.js +237 -0
  98. package/pathways/system/entity/tools/sys_tool_image.js +1 -1
  99. package/pathways/system/rest_streaming/sys_claude_37_sonnet.js +21 -0
  100. package/pathways/system/rest_streaming/sys_claude_41_opus.js +21 -0
  101. package/pathways/system/rest_streaming/sys_claude_4_sonnet.js +21 -0
  102. package/pathways/system/rest_streaming/sys_google_gemini_25_flash.js +25 -0
  103. package/pathways/system/rest_streaming/{sys_google_gemini_chat.js → sys_google_gemini_25_pro.js} +6 -4
  104. package/pathways/system/rest_streaming/sys_grok_4.js +23 -0
  105. package/pathways/system/rest_streaming/sys_grok_4_fast_non_reasoning.js +23 -0
  106. package/pathways/system/rest_streaming/sys_grok_4_fast_reasoning.js +23 -0
  107. package/pathways/system/rest_streaming/sys_openai_chat.js +3 -0
  108. package/pathways/system/rest_streaming/sys_openai_chat_gpt41.js +22 -0
  109. package/pathways/system/rest_streaming/sys_openai_chat_gpt41_mini.js +21 -0
  110. package/pathways/system/rest_streaming/sys_openai_chat_gpt41_nano.js +21 -0
  111. package/pathways/system/rest_streaming/{sys_claude_35_sonnet.js → sys_openai_chat_gpt4_omni.js} +6 -4
  112. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_omni_mini.js +21 -0
  113. package/pathways/system/rest_streaming/{sys_claude_3_haiku.js → sys_openai_chat_gpt5.js} +7 -5
  114. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_chat.js +21 -0
  115. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_mini.js +21 -0
  116. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_nano.js +21 -0
  117. package/pathways/system/rest_streaming/{sys_openai_chat_o1.js → sys_openai_chat_o3.js} +6 -3
  118. package/pathways/system/rest_streaming/sys_openai_chat_o3_mini.js +3 -0
  119. package/pathways/system/workspaces/run_workspace_prompt.js +99 -0
  120. package/pathways/vision.js +1 -1
  121. package/server/graphql.js +1 -1
  122. package/server/modelExecutor.js +8 -0
  123. package/server/pathwayResolver.js +166 -16
  124. package/server/pathwayResponseParser.js +16 -8
  125. package/server/plugins/azureFoundryAgentsPlugin.js +1 -1
  126. package/server/plugins/claude3VertexPlugin.js +193 -45
  127. package/server/plugins/gemini15ChatPlugin.js +21 -0
  128. package/server/plugins/gemini15VisionPlugin.js +360 -0
  129. package/server/plugins/googleCsePlugin.js +94 -0
  130. package/server/plugins/grokVisionPlugin.js +365 -0
  131. package/server/plugins/modelPlugin.js +3 -1
  132. package/server/plugins/openAiChatPlugin.js +106 -13
  133. package/server/plugins/openAiVisionPlugin.js +42 -30
  134. package/server/resolver.js +28 -4
  135. package/server/rest.js +270 -53
  136. package/server/typeDef.js +1 -0
  137. package/tests/{mocks.js → helpers/mocks.js} +5 -2
  138. package/tests/{server.js → helpers/server.js} +2 -2
  139. package/tests/helpers/sseAssert.js +23 -0
  140. package/tests/helpers/sseClient.js +73 -0
  141. package/tests/helpers/subscriptionAssert.js +11 -0
  142. package/tests/helpers/subscriptions.js +113 -0
  143. package/tests/{sublong.srt → integration/features/translate/sublong.srt} +4543 -4543
  144. package/tests/integration/features/translate/translate_chunking_stream.test.js +100 -0
  145. package/tests/{translate_srt.test.js → integration/features/translate/translate_srt.test.js} +2 -2
  146. package/tests/integration/graphql/async/stream/agentic.test.js +477 -0
  147. package/tests/integration/graphql/async/stream/subscription_streaming.test.js +62 -0
  148. package/tests/integration/graphql/async/stream/sys_entity_start_streaming.test.js +71 -0
  149. package/tests/integration/graphql/async/stream/vendors/claude_streaming.test.js +56 -0
  150. package/tests/integration/graphql/async/stream/vendors/gemini_streaming.test.js +66 -0
  151. package/tests/integration/graphql/async/stream/vendors/grok_streaming.test.js +56 -0
  152. package/tests/integration/graphql/async/stream/vendors/openai_streaming.test.js +72 -0
  153. package/tests/integration/graphql/features/google/sysToolGoogleSearch.test.js +96 -0
  154. package/tests/integration/graphql/features/grok/grok.test.js +688 -0
  155. package/tests/integration/graphql/features/grok/grok_x_search_tool.test.js +354 -0
  156. package/tests/{main.test.js → integration/graphql/features/main.test.js} +1 -1
  157. package/tests/{call_tools.test.js → integration/graphql/features/tools/call_tools.test.js} +2 -2
  158. package/tests/{vision.test.js → integration/graphql/features/vision/vision.test.js} +1 -1
  159. package/tests/integration/graphql/subscriptions/connection.test.js +26 -0
  160. package/tests/{openai_api.test.js → integration/rest/oai/openai_api.test.js} +63 -238
  161. package/tests/integration/rest/oai/tool_calling_api.test.js +343 -0
  162. package/tests/integration/rest/oai/tool_calling_streaming.test.js +85 -0
  163. package/tests/integration/rest/vendors/claude_streaming.test.js +47 -0
  164. package/tests/integration/rest/vendors/claude_tool_calling_streaming.test.js +75 -0
  165. package/tests/integration/rest/vendors/gemini_streaming.test.js +47 -0
  166. package/tests/integration/rest/vendors/gemini_tool_calling_streaming.test.js +75 -0
  167. package/tests/integration/rest/vendors/grok_streaming.test.js +55 -0
  168. package/tests/integration/rest/vendors/grok_tool_calling_streaming.test.js +75 -0
  169. package/tests/{azureAuthTokenHelper.test.js → unit/core/azureAuthTokenHelper.test.js} +1 -1
  170. package/tests/{chunkfunction.test.js → unit/core/chunkfunction.test.js} +2 -2
  171. package/tests/{config.test.js → unit/core/config.test.js} +3 -3
  172. package/tests/{encodeCache.test.js → unit/core/encodeCache.test.js} +1 -1
  173. package/tests/{fastLruCache.test.js → unit/core/fastLruCache.test.js} +1 -1
  174. package/tests/{handleBars.test.js → unit/core/handleBars.test.js} +1 -1
  175. package/tests/{memoryfunction.test.js → unit/core/memoryfunction.test.js} +2 -2
  176. package/tests/unit/core/mergeResolver.test.js +952 -0
  177. package/tests/{parser.test.js → unit/core/parser.test.js} +3 -3
  178. package/tests/unit/core/pathwayResolver.test.js +187 -0
  179. package/tests/{requestMonitor.test.js → unit/core/requestMonitor.test.js} +1 -1
  180. package/tests/{requestMonitorDurationEstimator.test.js → unit/core/requestMonitorDurationEstimator.test.js} +1 -1
  181. package/tests/{truncateMessages.test.js → unit/core/truncateMessages.test.js} +3 -3
  182. package/tests/{util.test.js → unit/core/util.test.js} +1 -1
  183. package/tests/{apptekTranslatePlugin.test.js → unit/plugins/apptekTranslatePlugin.test.js} +3 -3
  184. package/tests/{azureFoundryAgents.test.js → unit/plugins/azureFoundryAgents.test.js} +136 -1
  185. package/tests/{claude3VertexPlugin.test.js → unit/plugins/claude3VertexPlugin.test.js} +32 -10
  186. package/tests/{claude3VertexToolConversion.test.js → unit/plugins/claude3VertexToolConversion.test.js} +3 -3
  187. package/tests/unit/plugins/googleCsePlugin.test.js +111 -0
  188. package/tests/unit/plugins/grokVisionPlugin.test.js +1392 -0
  189. package/tests/{modelPlugin.test.js → unit/plugins/modelPlugin.test.js} +3 -3
  190. package/tests/{multimodal_conversion.test.js → unit/plugins/multimodal_conversion.test.js} +4 -4
  191. package/tests/{openAiChatPlugin.test.js → unit/plugins/openAiChatPlugin.test.js} +13 -4
  192. package/tests/{openAiToolPlugin.test.js → unit/plugins/openAiToolPlugin.test.js} +35 -27
  193. package/tests/{tokenHandlingTests.test.js → unit/plugins/tokenHandlingTests.test.js} +5 -5
  194. package/tests/{translate_apptek.test.js → unit/plugins/translate_apptek.test.js} +3 -3
  195. package/tests/{streaming.test.js → unit/plugins.streaming/plugin_stream_events.test.js} +19 -58
  196. package/helper-apps/mogrt-handler/tests/test-files/test.gif +0 -1
  197. package/helper-apps/mogrt-handler/tests/test-files/test.mogrt +0 -1
  198. package/helper-apps/mogrt-handler/tests/test-files/test.mp4 +0 -1
  199. package/pathways/system/rest_streaming/sys_openai_chat_gpt4.js +0 -19
  200. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_32.js +0 -19
  201. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_turbo.js +0 -19
  202. package/pathways/system/workspaces/run_claude35_sonnet.js +0 -21
  203. package/pathways/system/workspaces/run_claude3_haiku.js +0 -20
  204. package/pathways/system/workspaces/run_gpt35turbo.js +0 -20
  205. package/pathways/system/workspaces/run_gpt4.js +0 -20
  206. package/pathways/system/workspaces/run_gpt4_32.js +0 -20
  207. package/tests/agentic.test.js +0 -256
  208. package/tests/pathwayResolver.test.js +0 -78
  209. package/tests/subscription.test.js +0 -387
  210. /package/tests/{subchunk.srt → integration/features/translate/subchunk.srt} +0 -0
  211. /package/tests/{subhorizontal.srt → integration/features/translate/subhorizontal.srt} +0 -0
@@ -0,0 +1,100 @@
1
+ import test from 'ava';
2
+ import serverFactory from '../../../../index.js';
3
+ import { createWsClient, ensureWsConnection, collectSubscriptionEvents, validateProgressMessage } from '../../../helpers/subscriptions.js';
4
+
5
+ let testServer;
6
+ let wsClient;
7
+
8
+ test.before(async () => {
9
+ process.env.CORTEX_ENABLE_REST = 'true';
10
+ const { server, startServer } = await serverFactory();
11
+ startServer && await startServer();
12
+ testServer = server;
13
+
14
+ wsClient = createWsClient();
15
+ await ensureWsConnection(wsClient);
16
+ });
17
+
18
+ test.after.always('cleanup', async () => {
19
+ if (wsClient) wsClient.dispose();
20
+ if (testServer) await testServer.stop();
21
+ });
22
+
23
+ test.serial('Translate pathway handles chunked async processing correctly', async (t) => {
24
+ const longText = `In the heart of the bustling metropolis, where skyscrapers pierce the clouds and streets pulse with endless energy,
25
+ a story unfolds. It's a tale of innovation and perseverance, of dreams taking flight in the digital age.
26
+ Entrepreneurs and visionaries gather in gleaming office towers, their minds focused on the next breakthrough that will reshape our world.
27
+ In labs and workshops, engineers and designers collaborate, their fingers dancing across keyboards as they write the future in lines of code.
28
+ The city never sleeps, its rhythm maintained by the constant flow of ideas and ambition. Coffee shops become impromptu meeting rooms,
29
+ where startups are born on napkins and partnerships forged over steaming lattes. The energy is palpable, electric, contagious.
30
+ In the background, servers hum in vast data centers, processing countless transactions and storing the collective knowledge of humanity.
31
+ The digital revolution continues unabated, transforming how we live, work, and connect with one another.
32
+ Young graduates fresh from university mingle with seasoned veterans, each bringing their unique perspective to the challenges at hand.
33
+ The boundaries between traditional industries blur as technology weaves its way into every aspect of business and society.
34
+ This is the story of progress, of human ingenuity pushing the boundaries of what's possible.
35
+ It's a narrative that continues to evolve, page by digital page, in the great book of human achievement.`.repeat(10);
36
+
37
+ const response = await testServer.executeOperation({
38
+ query: `
39
+ query TestQuery($text: String!, $to: String!) {
40
+ translate_gpt4_omni(text: $text, to: $to, async: true) {
41
+ result
42
+ }
43
+ }
44
+ `,
45
+ variables: { text: longText, to: 'Spanish' }
46
+ });
47
+
48
+ const requestId = response.body?.singleResult?.data?.translate_gpt4_omni?.result;
49
+ t.truthy(requestId);
50
+
51
+ const events = await collectSubscriptionEvents(wsClient, {
52
+ query: `
53
+ subscription OnRequestProgress($requestId: String!) {
54
+ requestProgress(requestIds: [$requestId]) {
55
+ requestId
56
+ progress
57
+ data
58
+ info
59
+ }
60
+ }
61
+ `,
62
+ variables: { requestId },
63
+ }, 180000, { requireCompletion: true });
64
+
65
+ t.true(events.length > 0);
66
+
67
+ let lastProgress = -1;
68
+ let finalTranslation = null;
69
+ let progressValues = new Set();
70
+ let processingMessages = 0;
71
+
72
+ for (const event of events) {
73
+ const progress = event.data.requestProgress;
74
+ validateProgressMessage(t, progress, requestId);
75
+
76
+ if (progress.progress !== null) {
77
+ t.true(progress.progress >= lastProgress);
78
+ t.true(progress.progress >= 0 && progress.progress <= 1);
79
+ progressValues.add(progress.progress);
80
+ lastProgress = progress.progress;
81
+ }
82
+
83
+ if (progress.progress === 1) {
84
+ t.truthy(progress.data);
85
+ const parsed = JSON.parse(progress.data);
86
+ t.true(typeof parsed === 'string');
87
+ t.true(parsed.length > 0);
88
+ finalTranslation = parsed;
89
+ } else {
90
+ processingMessages++;
91
+ }
92
+ }
93
+
94
+ t.true(progressValues.size >= 2);
95
+ t.true(processingMessages >= 1);
96
+ t.is(lastProgress, 1);
97
+ t.truthy(finalTranslation);
98
+ });
99
+
100
+
@@ -1,11 +1,11 @@
1
1
  import test from 'ava';
2
- import serverFactory from '../index.js';
2
+ import serverFactory from '../../../../index.js';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { dirname } from 'path';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import { SubtitleUtils, parse } from '@aj-archipelago/subvibe';
8
- import { selectBestTranslation, splitIntoOverlappingChunks } from '../pathways/translate_subtitle.js';
8
+ import { selectBestTranslation, splitIntoOverlappingChunks } from '../../../../pathways/translate_subtitle.js';
9
9
 
10
10
  const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = dirname(__filename);
@@ -0,0 +1,477 @@
1
+ // agentic.test.js
2
+ // Tests for the agentic entity system
3
+
4
+ import test from 'ava';
5
+ import serverFactory from '../../../../../index.js';
6
+ import { createClient } from 'graphql-ws';
7
+ import ws from 'ws';
8
+
9
+ // Define models to test - 4.1 as default, include grok 4
10
+ const TEST_MODELS = [
11
+ 'oai-gpt41', // Default 4.1 model
12
+ 'xai-grok-4' // Grok 4 model
13
+ ];
14
+
15
+ let testServer;
16
+ let wsClient;
17
+
18
+ test.before(async () => {
19
+ process.env.CORTEX_ENABLE_REST = 'true';
20
+ const { server, startServer } = await serverFactory();
21
+ startServer && await startServer();
22
+ testServer = server;
23
+
24
+ // Create WebSocket client for subscriptions
25
+ wsClient = createClient({
26
+ url: `ws://localhost:${process.env.CORTEX_PORT || 4000}/graphql`,
27
+ webSocketImpl: ws,
28
+ retryAttempts: 3,
29
+ connectionParams: {},
30
+ on: {
31
+ error: (error) => {
32
+ console.error('WS connection error:', error);
33
+ }
34
+ }
35
+ });
36
+
37
+ // Test the connection by making a simple subscription
38
+ try {
39
+ await new Promise((resolve, reject) => {
40
+ const subscription = wsClient.subscribe(
41
+ {
42
+ query: `
43
+ subscription TestConnection {
44
+ requestProgress(requestIds: ["test"]) {
45
+ requestId
46
+ }
47
+ }
48
+ `
49
+ },
50
+ {
51
+ next: () => {
52
+ resolve();
53
+ },
54
+ error: reject,
55
+ complete: () => {
56
+ resolve();
57
+ }
58
+ }
59
+ );
60
+
61
+ // Add a timeout to avoid hanging
62
+ setTimeout(() => {
63
+ resolve();
64
+ }, 2000);
65
+ });
66
+ } catch (error) {
67
+ console.error('Failed to establish WebSocket connection:', error);
68
+ throw error;
69
+ }
70
+ });
71
+
72
+ test.after.always('cleanup', async () => {
73
+ if (wsClient) {
74
+ wsClient.dispose();
75
+ }
76
+ if (testServer) {
77
+ await testServer.stop();
78
+ }
79
+ });
80
+
81
+ // Helper function to collect subscription events
82
+ async function collectSubscriptionEvents(subscription, timeout = 30000) {
83
+ const events = [];
84
+
85
+ return new Promise((resolve, reject) => {
86
+ const timeoutId = setTimeout(() => {
87
+ if (events.length > 0) {
88
+ resolve(events);
89
+ } else {
90
+ reject(new Error('Subscription timed out with no events'));
91
+ }
92
+ }, timeout);
93
+
94
+ const unsubscribe = wsClient.subscribe(
95
+ {
96
+ query: subscription.query,
97
+ variables: subscription.variables
98
+ },
99
+ {
100
+ next: (event) => {
101
+ events.push(event);
102
+ if (event?.data?.requestProgress?.progress === 1) {
103
+ clearTimeout(timeoutId);
104
+ unsubscribe();
105
+ resolve(events);
106
+ }
107
+ },
108
+ error: (error) => {
109
+ clearTimeout(timeoutId);
110
+ reject(error);
111
+ },
112
+ complete: () => {
113
+ clearTimeout(timeoutId);
114
+ resolve(events);
115
+ }
116
+ }
117
+ );
118
+ });
119
+ }
120
+
121
+ // Helper function to create model-specific tests
122
+ function createModelTest(testName, testFunction) {
123
+ TEST_MODELS.forEach(model => {
124
+ test.serial(`${testName} - ${model}`, async (t) => {
125
+ await testFunction(t, model);
126
+ });
127
+ });
128
+ }
129
+
130
+ // Helper function to validate info object structure
131
+ function validateInfoObject(t, info, testName) {
132
+ if (!info || typeof info !== 'object') {
133
+ t.fail(`${testName}: Info object should be a valid object`);
134
+ return;
135
+ }
136
+
137
+ // Check if info object has any meaningful content
138
+ const hasContent = Object.keys(info).length > 0;
139
+ t.true(hasContent, `${testName}: Info object should have some content`);
140
+
141
+ // Validate toolUsed if present
142
+ if (info.toolUsed) {
143
+ if (Array.isArray(info.toolUsed)) {
144
+ t.true(info.toolUsed.length > 0, `${testName}: toolUsed array should not be empty`);
145
+ info.toolUsed.forEach((tool, index) => {
146
+ // Handle nested arrays in toolUsed
147
+ if (Array.isArray(tool)) {
148
+ t.true(tool.length > 0, `${testName}: toolUsed[${index}] nested array should not be empty`);
149
+ tool.forEach((nestedTool, nestedIndex) => {
150
+ t.true(typeof nestedTool === 'string', `${testName}: toolUsed[${index}][${nestedIndex}] should be a string`);
151
+ t.truthy(nestedTool.trim(), `${testName}: toolUsed[${index}][${nestedIndex}] should not be empty`);
152
+ });
153
+ } else {
154
+ t.true(typeof tool === 'string', `${testName}: toolUsed[${index}] should be a string`);
155
+ t.truthy(tool.trim(), `${testName}: toolUsed[${index}] should not be empty`);
156
+ }
157
+ });
158
+ } else {
159
+ t.true(typeof info.toolUsed === 'string', `${testName}: toolUsed should be a string if not array`);
160
+ t.truthy(info.toolUsed.trim(), `${testName}: toolUsed should not be empty`);
161
+ }
162
+ }
163
+
164
+ // Validate citations if present
165
+ if (info.citations) {
166
+ t.true(Array.isArray(info.citations), `${testName}: citations should be an array`);
167
+ info.citations.forEach((citation, index) => {
168
+ t.true(typeof citation === 'object', `${testName}: citations[${index}] should be an object`);
169
+ if (citation.title) {
170
+ t.true(typeof citation.title === 'string', `${testName}: citations[${index}].title should be a string`);
171
+ }
172
+ if (citation.url) {
173
+ t.true(typeof citation.url === 'string', `${testName}: citations[${index}].url should be a string`);
174
+ // URLs can be empty strings, which is valid
175
+ if (citation.url.trim()) {
176
+ t.true(citation.url.startsWith('http'), `${testName}: citations[${index}].url should be a valid URL if not empty`);
177
+ }
178
+ }
179
+ if (citation.content) {
180
+ t.true(typeof citation.content === 'string', `${testName}: citations[${index}].content should be a string`);
181
+ }
182
+ });
183
+ }
184
+
185
+ // Validate toolCalls if present
186
+ if (info.toolCalls) {
187
+ t.true(Array.isArray(info.toolCalls), `${testName}: toolCalls should be an array`);
188
+ info.toolCalls.forEach((toolCall, index) => {
189
+ t.true(typeof toolCall === 'object', `${testName}: toolCalls[${index}] should be an object`);
190
+ if (toolCall.name) {
191
+ t.true(typeof toolCall.name === 'string', `${testName}: toolCalls[${index}].name should be a string`);
192
+ }
193
+ });
194
+ }
195
+
196
+ // Validate usage if present
197
+ if (info.usage) {
198
+ // Handle both single usage object and array of usage objects
199
+ if (Array.isArray(info.usage)) {
200
+ t.true(info.usage.length > 0, `${testName}: usage array should not be empty`);
201
+ info.usage.forEach((usage, index) => {
202
+ t.true(typeof usage === 'object', `${testName}: usage[${index}] should be an object`);
203
+ if (usage.prompt_tokens !== undefined) {
204
+ t.true(typeof usage.prompt_tokens === 'number', `${testName}: usage[${index}].prompt_tokens should be a number`);
205
+ }
206
+ if (usage.completion_tokens !== undefined) {
207
+ t.true(typeof usage.completion_tokens === 'number', `${testName}: usage[${index}].completion_tokens should be a number`);
208
+ }
209
+ if (usage.total_tokens !== undefined) {
210
+ t.true(typeof usage.total_tokens === 'number', `${testName}: usage[${index}].total_tokens should be a number`);
211
+ }
212
+ });
213
+ } else {
214
+ t.true(typeof info.usage === 'object', `${testName}: usage should be an object`);
215
+ if (info.usage.prompt_tokens !== undefined) {
216
+ t.true(typeof info.usage.prompt_tokens === 'number', `${testName}: usage.prompt_tokens should be a number`);
217
+ }
218
+ if (info.usage.completion_tokens !== undefined) {
219
+ t.true(typeof info.usage.completion_tokens === 'number', `${testName}: usage.completion_tokens should be a number`);
220
+ }
221
+ if (info.usage.total_tokens !== undefined) {
222
+ t.true(typeof info.usage.total_tokens === 'number', `${testName}: usage.total_tokens should be a number`);
223
+ }
224
+ }
225
+ }
226
+
227
+ // Validate finishReason if present
228
+ if (info.finishReason) {
229
+ t.true(typeof info.finishReason === 'string', `${testName}: finishReason should be a string`);
230
+ const validReasons = ['stop', 'length', 'tool_calls', 'content_filter', 'function_call'];
231
+ t.true(validReasons.includes(info.finishReason), `${testName}: finishReason should be a valid reason`);
232
+ }
233
+ }
234
+
235
+ // Helper function to flatten nested arrays
236
+ function flattenArray(arr) {
237
+ const result = [];
238
+ for (const item of arr) {
239
+ if (Array.isArray(item)) {
240
+ result.push(...flattenArray(item));
241
+ } else {
242
+ result.push(item);
243
+ }
244
+ }
245
+ return result;
246
+ }
247
+
248
+ // Test basic single-step task
249
+ createModelTest('sys_entity_agent handles single-step task', async (t, model) => {
250
+ t.timeout(60000); // 60 second timeout
251
+ const response = await testServer.executeOperation({
252
+ query: `
253
+ query TestAgentSingleStep(
254
+ $text: String!,
255
+ $chatHistory: [MultiMessage]!,
256
+ $model: String
257
+ ) {
258
+ sys_entity_agent(
259
+ text: $text,
260
+ chatHistory: $chatHistory,
261
+ model: $model,
262
+ stream: true
263
+ ) {
264
+ result
265
+ contextId
266
+ tool
267
+ warnings
268
+ errors
269
+ }
270
+ }
271
+ `,
272
+ variables: {
273
+ text: 'What is the current time in Los Angeles?',
274
+ chatHistory: [{
275
+ role: "user",
276
+ content: ["What is the current time in Los Angeles?"]
277
+ }],
278
+ model: model
279
+ }
280
+ });
281
+
282
+ console.log(`Single-step Agent Response (${model}):`, JSON.stringify(response, null, 2));
283
+
284
+ // Check for successful response
285
+ t.falsy(response.body?.singleResult?.errors, 'Should not have GraphQL errors');
286
+ const requestId = response.body?.singleResult?.data?.sys_entity_agent?.result;
287
+ t.truthy(requestId, 'Should have a requestId in the result field');
288
+
289
+ // Collect events
290
+ const events = await collectSubscriptionEvents({
291
+ query: `
292
+ subscription OnRequestProgress($requestId: String!) {
293
+ requestProgress(requestIds: [$requestId]) {
294
+ requestId
295
+ progress
296
+ data
297
+ info
298
+ }
299
+ }
300
+ `,
301
+ variables: { requestId }
302
+ });
303
+
304
+ console.log(`Received ${events.length} events for single-step task (${model})`);
305
+ t.true(events.length > 0, 'Should have received events');
306
+
307
+ // Verify we got a completion event
308
+ const completionEvent = events.find(event =>
309
+ event.data.requestProgress.progress === 1
310
+ );
311
+ t.truthy(completionEvent, 'Should have received a completion event');
312
+
313
+ // Validate the info object in the completion event
314
+ const infoString = completionEvent.data.requestProgress.info;
315
+
316
+ // For single-step tasks, info might be empty or an empty object, which is acceptable
317
+ if (infoString && infoString.trim()) {
318
+ let infoObject;
319
+ try {
320
+ infoObject = JSON.parse(infoString);
321
+ } catch (error) {
322
+ t.fail(`Failed to parse info object: ${error.message}`);
323
+ return;
324
+ }
325
+
326
+ // Validate the info object structure
327
+ if (Object.keys(infoObject).length > 0) {
328
+ validateInfoObject(t, infoObject, 'Single-step task');
329
+ }
330
+
331
+ // For single-step tasks, we might not have tool usage, but we should have other properties
332
+ if (infoObject.finishReason) {
333
+ t.true(['stop', 'length', 'tool_calls', 'content_filter'].includes(infoObject.finishReason),
334
+ 'Single-step task should have a valid finish reason');
335
+ }
336
+ console.log(`Single-step info object validation passed for ${model}:`, JSON.stringify(infoObject, null, 2));
337
+ } else {
338
+ console.log(`Single-step task (${model}) completed without info object - this is acceptable for simple tasks`);
339
+ }
340
+ });
341
+
342
+ // Test multi-step task with tool usage
343
+ createModelTest('sys_entity_agent handles multi-step task with tools', async (t, model) => {
344
+ t.timeout(360000); // 120 second timeout for multi-step task
345
+ const response = await testServer.executeOperation({
346
+ query: `
347
+ query TestAgentMultiStep(
348
+ $text: String!,
349
+ $chatHistory: [MultiMessage]!,
350
+ $model: String
351
+ ) {
352
+ sys_entity_agent(
353
+ text: $text,
354
+ chatHistory: $chatHistory,
355
+ model: $model,
356
+ stream: true
357
+ ) {
358
+ result
359
+ contextId
360
+ tool
361
+ warnings
362
+ errors
363
+ }
364
+ }
365
+ `,
366
+ variables: {
367
+ text: 'Research the latest developments in renewable energy and summarize the key trends.',
368
+ chatHistory: [{
369
+ role: "user",
370
+ content: ["Research the latest developments in renewable energy and summarize the key trends."]
371
+ }],
372
+ model: model
373
+ }
374
+ });
375
+
376
+ console.log(`Multi-step Agent Response (${model}):`, JSON.stringify(response, null, 2));
377
+
378
+ // Check for successful response
379
+ t.falsy(response.body?.singleResult?.errors, 'Should not have GraphQL errors');
380
+ const requestId = response.body?.singleResult?.data?.sys_entity_agent?.result;
381
+ t.truthy(requestId, 'Should have a requestId in the result field');
382
+
383
+ // Collect events with a longer timeout since this is a multi-step operation
384
+ const events = await collectSubscriptionEvents({
385
+ query: `
386
+ subscription OnRequestProgress($requestId: String!) {
387
+ requestProgress(requestIds: [$requestId]) {
388
+ requestId
389
+ progress
390
+ data
391
+ info
392
+ }
393
+ }
394
+ `,
395
+ variables: { requestId }
396
+ }, 240000);
397
+
398
+ console.log(`Received ${events.length} events for multi-step task (${model})`);
399
+ t.true(events.length > 0, 'Should have received events');
400
+
401
+ // Verify we got a completion event
402
+ const completionEvent = events.find(event =>
403
+ event.data.requestProgress.progress === 1
404
+ );
405
+ t.truthy(completionEvent, 'Should have received a completion event');
406
+
407
+ const infoString = completionEvent.data.requestProgress.info;
408
+ t.truthy(infoString, 'Multi-step task should have info object');
409
+
410
+ let infoObject;
411
+ try {
412
+ infoObject = JSON.parse(infoString);
413
+ } catch (error) {
414
+ t.fail(`Failed to parse info object: ${error.message}`);
415
+ return;
416
+ }
417
+
418
+ // Validate the info object structure
419
+ validateInfoObject(t, infoObject, 'Multi-step task');
420
+
421
+ // Additional specific validations for multi-step tasks
422
+ if (infoObject.toolUsed) {
423
+ // For multi-step tasks, we expect multiple tools to be used
424
+ const toolUsedArray = Array.isArray(infoObject.toolUsed) ? infoObject.toolUsed : [infoObject.toolUsed];
425
+ t.true(toolUsedArray.length > 0, 'Multi-step task should have used at least one tool');
426
+
427
+ // Flatten nested arrays for tool counting
428
+ const flattenedTools = flattenArray(toolUsedArray);
429
+ t.true(flattenedTools.length > 0, 'Multi-step task should have used tools after flattening');
430
+
431
+ // Check for common tool types that should be used in research tasks
432
+ const expectedToolTypes = ['Search', 'SearchInternetAgent2', 'SearchXPlatform', 'WebPageContent', 'SearchAgent'];
433
+ const hasExpectedTool = flattenedTools.some(tool =>
434
+ expectedToolTypes.some(expectedType => tool.includes(expectedType))
435
+ );
436
+ t.true(hasExpectedTool, 'Multi-step research task should have used search tools');
437
+ }
438
+
439
+ // Validate citations for research tasks
440
+ if (infoObject.citations) {
441
+ t.true(infoObject.citations.length > 0, 'Research task should have citations');
442
+ infoObject.citations.forEach((citation, index) => {
443
+ // Citations should have either URL or content, but not necessarily both
444
+ t.truthy(citation.url || citation.content, `Citation ${index} should have URL or content`);
445
+ if (citation.title || citation.content) {
446
+ t.truthy(citation.title || citation.content, `Citation ${index} should have title or content`);
447
+ }
448
+ });
449
+ }
450
+
451
+ // Validate toolCalls for multi-step tasks
452
+ if (infoObject.toolCalls) {
453
+ t.true(infoObject.toolCalls.length > 0, 'Multi-step task should have tool calls');
454
+ }
455
+
456
+ // Validate usage statistics
457
+ if (infoObject.usage) {
458
+ // Handle both single usage object and array of usage objects
459
+ if (Array.isArray(infoObject.usage)) {
460
+ t.true(infoObject.usage.length > 0, 'Multi-step task should have usage data');
461
+ const latestUsage = infoObject.usage[0]; // Most recent usage first
462
+ t.truthy(latestUsage.total_tokens, 'Multi-step task should have total token usage');
463
+ t.true(latestUsage.total_tokens > 0, 'Multi-step task should have used tokens');
464
+ } else {
465
+ t.truthy(infoObject.usage.total_tokens, 'Multi-step task should have total token usage');
466
+ t.true(infoObject.usage.total_tokens > 0, 'Multi-step task should have used tokens');
467
+ }
468
+ }
469
+
470
+ // Validate finish reason
471
+ if (infoObject.finishReason) {
472
+ t.true(['stop', 'length', 'tool_calls', 'content_filter'].includes(infoObject.finishReason),
473
+ 'Multi-step task should have a valid finish reason');
474
+ }
475
+
476
+ console.log(`Multi-step info object validation passed for ${model}:`, JSON.stringify(infoObject, null, 2));
477
+ });
@@ -0,0 +1,62 @@
1
+ // Consolidated GraphQL subscription streaming tests (subset from subscription.test.js)
2
+
3
+ import test from 'ava';
4
+ import serverFactory from '../../../../../index.js';
5
+ import { createWsClient, ensureWsConnection, collectSubscriptionEvents, validateProgressMessage } from '../../../../helpers/subscriptions.js';
6
+
7
+ let testServer;
8
+ let wsClient;
9
+
10
+ test.before(async () => {
11
+ process.env.CORTEX_ENABLE_REST = 'true';
12
+ const { server, startServer } = await serverFactory();
13
+ startServer && await startServer();
14
+ testServer = server;
15
+
16
+ wsClient = createWsClient();
17
+ await ensureWsConnection(wsClient);
18
+ });
19
+
20
+ test.after.always('cleanup', async () => {
21
+ if (wsClient) wsClient.dispose();
22
+ if (testServer) await testServer.stop();
23
+ });
24
+
25
+ test.serial('Request progress messages have string data and info fields', async (t) => {
26
+ const response = await testServer.executeOperation({
27
+ query: `
28
+ query TestQuery($text: String!) {
29
+ chat(text: $text, async: true, stream: true) {
30
+ result
31
+ }
32
+ }
33
+ `,
34
+ variables: { text: 'Generate a long response to test streaming' }
35
+ });
36
+
37
+ const requestId = response.body?.singleResult?.data?.chat?.result;
38
+ t.truthy(requestId);
39
+
40
+ const events = await collectSubscriptionEvents(wsClient, {
41
+ query: `
42
+ subscription OnRequestProgress($requestId: String!) {
43
+ requestProgress(requestIds: [$requestId]) {
44
+ requestId
45
+ progress
46
+ data
47
+ info
48
+ }
49
+ }
50
+ `,
51
+ variables: { requestId }
52
+ }, 10000, { requireCompletion: false, minEvents: 1 });
53
+
54
+ t.true(events.length > 0);
55
+
56
+ for (const event of events) {
57
+ const progress = event.data.requestProgress;
58
+ validateProgressMessage(t, progress, requestId);
59
+ }
60
+ });
61
+
62
+