@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,1392 @@
1
+ // grokVisionPlugin.test.js
2
+ // This file contains direct plugin tests for GrokVisionPlugin (non-connecting tests)
3
+
4
+ import test from 'ava';
5
+ import GrokVisionPlugin from '../../../server/plugins/grokVisionPlugin.js';
6
+ import { safeJsonParse } from '../../../server/plugins/grokVisionPlugin.js';
7
+
8
+ test('should create GrokVisionPlugin instance', t => {
9
+ const mockPathway = {
10
+ name: 'test-pathway',
11
+ temperature: 0.7,
12
+ prompt: 'Test prompt'
13
+ };
14
+
15
+ const mockModel = {
16
+ name: 'xai-grok-3',
17
+ type: 'GROK-VISION',
18
+ url: 'https://api.x.ai/v1/chat/completions',
19
+ headers: {
20
+ 'Authorization': 'Bearer test-key',
21
+ 'Content-Type': 'application/json'
22
+ },
23
+ params: {
24
+ model: 'grok-3-latest'
25
+ },
26
+ maxTokenLength: 131072,
27
+ maxReturnTokens: 4096
28
+ };
29
+
30
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
31
+
32
+ t.true(plugin instanceof GrokVisionPlugin);
33
+ t.is(plugin.modelName, 'xai-grok-3');
34
+ t.true(plugin.isMultiModal);
35
+ });
36
+
37
+ test('should handle Grok-specific parameters', async t => {
38
+ const mockPathway = {
39
+ name: 'test-pathway',
40
+ temperature: 0.7,
41
+ prompt: 'Test prompt'
42
+ };
43
+
44
+ const mockModel = {
45
+ name: 'xai-grok-4',
46
+ type: 'GROK-VISION',
47
+ url: 'https://api.x.ai/v1/chat/completions',
48
+ headers: {
49
+ 'Authorization': 'Bearer test-key',
50
+ 'Content-Type': 'application/json'
51
+ },
52
+ params: {
53
+ model: 'grok-4-0709'
54
+ },
55
+ maxTokenLength: 131072,
56
+ maxReturnTokens: 4096
57
+ };
58
+
59
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
60
+
61
+ const parameters = {
62
+ search_parameters: '{"mode": "auto"}'
63
+ };
64
+
65
+ const requestParams = await plugin.getRequestParameters('test text', parameters, {});
66
+
67
+ t.truthy(requestParams.search_parameters);
68
+ t.is(requestParams.search_parameters.mode, 'auto');
69
+ // Vision parameters are handled in message content, not as top-level parameters
70
+ });
71
+
72
+ test('should handle all Live Search parameters correctly', async t => {
73
+ const mockPathway = {
74
+ name: 'test-pathway',
75
+ temperature: 0.7,
76
+ prompt: 'Test prompt'
77
+ };
78
+
79
+ const mockModel = {
80
+ name: 'xai-grok-3',
81
+ type: 'GROK-VISION',
82
+ url: 'https://api.x.ai/v1/chat/completions',
83
+ headers: {
84
+ 'Authorization': 'Bearer test-key',
85
+ 'Content-Type': 'application/json'
86
+ },
87
+ params: {
88
+ model: 'grok-3-latest'
89
+ },
90
+ maxTokenLength: 131072,
91
+ maxReturnTokens: 4096
92
+ };
93
+
94
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
95
+
96
+ // Test comprehensive Live Search parameters as JSON string
97
+ const parameters = {
98
+ search_parameters: JSON.stringify({
99
+ mode: 'on',
100
+ return_citations: true,
101
+ from_date: '2024-01-01',
102
+ to_date: '2024-12-31',
103
+ max_search_results: 15,
104
+ sources: [
105
+ { type: 'web', country: 'US', excluded_websites: ['wikipedia.org'] },
106
+ { type: 'x', included_x_handles: ['xai'], post_favorite_count: 1000 },
107
+ { type: 'news', safe_search: false },
108
+ { type: 'rss', links: ['https://status.x.ai/feed.xml'] }
109
+ ]
110
+ })
111
+ };
112
+
113
+ const requestParams = await plugin.getRequestParameters('Search for latest tech news', parameters, {});
114
+
115
+ // Verify all search_parameters are set correctly
116
+ t.truthy(requestParams.search_parameters, 'search_parameters should be set');
117
+ t.is(requestParams.search_parameters.mode, 'on', 'search mode should be on');
118
+ t.true(requestParams.search_parameters.return_citations, 'return_citations should be true');
119
+ t.is(requestParams.search_parameters.from_date, '2024-01-01', 'from_date should be set');
120
+ t.is(requestParams.search_parameters.to_date, '2024-12-31', 'to_date should be set');
121
+ t.is(requestParams.search_parameters.max_search_results, 15, 'max_search_results should be 15');
122
+ t.truthy(requestParams.search_parameters.sources, 'sources should be set');
123
+ t.is(requestParams.search_parameters.sources.length, 4, 'should have 4 sources');
124
+
125
+ // Verify web source
126
+ const webSource = requestParams.search_parameters.sources.find(s => s.type === 'web');
127
+ t.truthy(webSource, 'web source should exist');
128
+ t.is(webSource.country, 'US', 'web source country should be US');
129
+ t.deepEqual(webSource.excluded_websites, ['wikipedia.org'], 'web source excluded_websites should be set');
130
+
131
+ // Verify X source
132
+ const xSource = requestParams.search_parameters.sources.find(s => s.type === 'x');
133
+ t.truthy(xSource, 'x source should exist');
134
+ t.deepEqual(xSource.included_x_handles, ['xai'], 'x source included_x_handles should be set');
135
+ t.is(xSource.post_favorite_count, 1000, 'x source post_favorite_count should be 1000');
136
+
137
+ // Verify news source
138
+ const newsSource = requestParams.search_parameters.sources.find(s => s.type === 'news');
139
+ t.truthy(newsSource, 'news source should exist');
140
+ t.false(newsSource.safe_search, 'news source safe_search should be false');
141
+
142
+ // Verify RSS source
143
+ const rssSource = requestParams.search_parameters.sources.find(s => s.type === 'rss');
144
+ t.truthy(rssSource, 'rss source should exist');
145
+ t.deepEqual(rssSource.links, ['https://status.x.ai/feed.xml'], 'rss source links should be set');
146
+ });
147
+
148
+ test('should handle individual source parameters correctly', async t => {
149
+ const mockPathway = {
150
+ name: 'test-pathway',
151
+ temperature: 0.7,
152
+ prompt: 'Test prompt'
153
+ };
154
+
155
+ const mockModel = {
156
+ name: 'xai-grok-3',
157
+ type: 'GROK-VISION',
158
+ url: 'https://api.x.ai/v1/chat/completions',
159
+ headers: {
160
+ 'Authorization': 'Bearer test-key',
161
+ 'Content-Type': 'application/json'
162
+ },
163
+ params: {
164
+ model: 'grok-3-latest'
165
+ },
166
+ maxTokenLength: 131072,
167
+ maxReturnTokens: 4096
168
+ };
169
+
170
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
171
+
172
+ // Test web source with allowed_websites
173
+ const webParameters = {
174
+ search_parameters: JSON.stringify({
175
+ mode: 'auto',
176
+ sources: [{ type: 'web', allowed_websites: ['x.ai', 'github.com'] }]
177
+ })
178
+ };
179
+
180
+ const webRequestParams = await plugin.getRequestParameters('Search xAI website', webParameters, {});
181
+ const webSource = webRequestParams.search_parameters.sources[0];
182
+ t.deepEqual(webSource.allowed_websites, ['x.ai', 'github.com'], 'web source allowed_websites should be set');
183
+
184
+ // Test X source with excluded_handles and view count
185
+ const xParameters = {
186
+ search_parameters: JSON.stringify({
187
+ mode: 'auto',
188
+ sources: [{
189
+ type: 'x',
190
+ excluded_x_handles: ['spam_account'],
191
+ post_view_count: 50000
192
+ }]
193
+ })
194
+ };
195
+
196
+ const xRequestParams = await plugin.getRequestParameters('Search X posts', xParameters, {});
197
+ const xSource = xRequestParams.search_parameters.sources[0];
198
+ t.deepEqual(xSource.excluded_x_handles, ['spam_account'], 'x source excluded_x_handles should be set');
199
+ t.is(xSource.post_view_count, 50000, 'x source post_view_count should be set');
200
+ });
201
+
202
+ test('should handle date range parameters correctly', async t => {
203
+ const mockPathway = {
204
+ name: 'test-pathway',
205
+ temperature: 0.7,
206
+ prompt: 'Test prompt'
207
+ };
208
+
209
+ const mockModel = {
210
+ name: 'xai-grok-3',
211
+ type: 'GROK-VISION',
212
+ url: 'https://api.x.ai/v1/chat/completions',
213
+ headers: {
214
+ 'Authorization': 'Bearer test-key',
215
+ 'Content-Type': 'application/json'
216
+ },
217
+ params: {
218
+ model: 'grok-3-latest'
219
+ },
220
+ maxTokenLength: 131072,
221
+ maxReturnTokens: 4096
222
+ };
223
+
224
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
225
+
226
+ // Test with only from_date
227
+ const fromDateParameters = {
228
+ search_parameters: JSON.stringify({
229
+ mode: 'auto',
230
+ from_date: '2024-06-01'
231
+ })
232
+ };
233
+
234
+ const fromDateRequestParams = await plugin.getRequestParameters('Search recent news', fromDateParameters, {});
235
+ t.is(fromDateRequestParams.search_parameters.from_date, '2024-06-01', 'from_date should be set');
236
+ t.is(fromDateRequestParams.search_parameters.to_date, undefined, 'to_date should not be set');
237
+
238
+ // Test with only to_date
239
+ const toDateParameters = {
240
+ search_parameters: JSON.stringify({
241
+ mode: 'auto',
242
+ to_date: '2024-12-31'
243
+ })
244
+ };
245
+
246
+ const toDateRequestParams = await plugin.getRequestParameters('Search historical data', toDateParameters, {});
247
+ t.is(toDateRequestParams.search_parameters.to_date, '2024-12-31', 'to_date should be set');
248
+ t.is(toDateRequestParams.search_parameters.from_date, undefined, 'from_date should not be set');
249
+
250
+ // Test with both dates
251
+ const bothDatesParameters = {
252
+ search_parameters: JSON.stringify({
253
+ mode: 'auto',
254
+ from_date: '2024-01-01',
255
+ to_date: '2024-06-30'
256
+ })
257
+ };
258
+
259
+ const bothDatesRequestParams = await plugin.getRequestParameters('Search specific period', bothDatesParameters, {});
260
+ t.is(bothDatesRequestParams.search_parameters.from_date, '2024-01-01', 'from_date should be set');
261
+ t.is(bothDatesRequestParams.search_parameters.to_date, '2024-06-30', 'to_date should be set');
262
+ });
263
+
264
+ test('should handle empty search_parameters correctly', async t => {
265
+ const mockPathway = {
266
+ name: 'test-pathway',
267
+ temperature: 0.7,
268
+ prompt: 'Test prompt'
269
+ };
270
+
271
+ const mockModel = {
272
+ name: 'xai-grok-3',
273
+ type: 'GROK-VISION',
274
+ url: 'https://api.x.ai/v1/chat/completions',
275
+ headers: {
276
+ 'Authorization': 'Bearer test-key',
277
+ 'Content-Type': 'application/json'
278
+ },
279
+ params: {
280
+ model: 'grok-3-latest'
281
+ },
282
+ maxTokenLength: 131072,
283
+ maxReturnTokens: 4096
284
+ };
285
+
286
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
287
+
288
+ // Test with empty search_parameters (should not be set)
289
+ const emptyParameters = {};
290
+
291
+ const emptyRequestParams = await plugin.getRequestParameters('Search with defaults', emptyParameters, {});
292
+ t.is(emptyRequestParams.search_parameters, undefined, 'search_parameters should not be set when not provided');
293
+
294
+ // Test with empty JSON string
295
+ const emptyJsonParameters = {
296
+ search_parameters: '{}'
297
+ };
298
+
299
+ const emptyJsonRequestParams = await plugin.getRequestParameters('Search with empty json', emptyJsonParameters, {});
300
+ t.is(emptyJsonRequestParams.search_parameters, undefined, 'search_parameters should not be set when empty object provided');
301
+ });
302
+
303
+ // Note: grok_live_search pathway test removed as the pathway file does not exist yet
304
+
305
+ test('should handle X.AI vision message structure correctly', async t => {
306
+ const mockPathway = {
307
+ name: 'test-pathway',
308
+ temperature: 0.7,
309
+ prompt: 'Test prompt'
310
+ };
311
+
312
+ const mockModel = {
313
+ name: 'xai-grok-3',
314
+ type: 'GROK-VISION',
315
+ url: 'https://api.x.ai/v1/chat/completions',
316
+ headers: {
317
+ 'Authorization': 'Bearer test-key',
318
+ 'Content-Type': 'application/json'
319
+ },
320
+ params: {
321
+ model: 'grok-3-latest'
322
+ },
323
+ maxTokenLength: 131072,
324
+ maxReturnTokens: 4096
325
+ };
326
+
327
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
328
+
329
+ // Test vision message with URL image
330
+ const visionMessages = [
331
+ {
332
+ role: 'user',
333
+ content: [
334
+ {
335
+ type: 'image_url',
336
+ image_url: {
337
+ url: 'https://example.com/image.jpg',
338
+ detail: 'high'
339
+ }
340
+ },
341
+ {
342
+ type: 'text',
343
+ text: 'What is in this image?'
344
+ }
345
+ ]
346
+ }
347
+ ];
348
+
349
+ const parameters = {};
350
+
351
+ const requestParams = await plugin.getRequestParameters('', parameters, {});
352
+ requestParams.messages = visionMessages;
353
+
354
+ // Verify vision message structure
355
+ t.is(requestParams.messages[0].content.length, 2, 'should have 2 content items');
356
+ t.is(requestParams.messages[0].content[0].type, 'image_url', 'first item should be image_url');
357
+ t.is(requestParams.messages[0].content[1].type, 'text', 'second item should be text');
358
+ t.is(requestParams.messages[0].content[0].image_url.detail, 'high', 'image detail should be high');
359
+ t.is(requestParams.messages[0].content[0].image_url.url, 'https://example.com/image.jpg', 'image URL should be set');
360
+ });
361
+
362
+ test('should handle X.AI vision with base64 images', async t => {
363
+ const mockPathway = {
364
+ name: 'test-pathway',
365
+ temperature: 0.7,
366
+ prompt: 'Test prompt'
367
+ };
368
+
369
+ const mockModel = {
370
+ name: 'xai-grok-3',
371
+ type: 'GROK-VISION',
372
+ url: 'https://api.x.ai/v1/chat/completions',
373
+ headers: {
374
+ 'Authorization': 'Bearer test-key',
375
+ 'Content-Type': 'application/json'
376
+ },
377
+ params: {
378
+ model: 'grok-3-latest'
379
+ },
380
+ maxTokenLength: 131072,
381
+ maxReturnTokens: 4096
382
+ };
383
+
384
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
385
+
386
+ // Test vision message with base64 image
387
+ const base64Image = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k=';
388
+
389
+ const visionMessages = [
390
+ {
391
+ role: 'user',
392
+ content: [
393
+ {
394
+ type: 'text',
395
+ text: 'What is in this image?'
396
+ },
397
+ {
398
+ type: 'image_url',
399
+ image_url: {
400
+ url: base64Image,
401
+ detail: 'auto'
402
+ }
403
+ }
404
+ ]
405
+ }
406
+ ];
407
+
408
+ const parameters = {};
409
+
410
+ const requestParams = await plugin.getRequestParameters('', parameters, {});
411
+ requestParams.messages = visionMessages;
412
+
413
+ // Verify base64 image message structure
414
+ t.is(requestParams.messages[0].content.length, 2, 'should have 2 content items');
415
+ t.is(requestParams.messages[0].content[0].type, 'text', 'first item should be text');
416
+ t.is(requestParams.messages[0].content[1].type, 'image_url', 'second item should be image_url');
417
+ t.is(requestParams.messages[0].content[1].image_url.detail, 'auto', 'image detail should be auto');
418
+ t.true(requestParams.messages[0].content[1].image_url.url.startsWith('data:image/jpeg;base64,'), 'should be base64 image');
419
+ });
420
+
421
+ test('should handle X.AI vision with multiple images', async t => {
422
+ const mockPathway = {
423
+ name: 'test-pathway',
424
+ temperature: 0.7,
425
+ prompt: 'Test prompt'
426
+ };
427
+
428
+ const mockModel = {
429
+ name: 'xai-grok-3',
430
+ type: 'GROK-VISION',
431
+ url: 'https://api.x.ai/v1/chat/completions',
432
+ headers: {
433
+ 'Authorization': 'Bearer test-key',
434
+ 'Content-Type': 'application/json'
435
+ },
436
+ params: {
437
+ model: 'grok-3-latest'
438
+ },
439
+ maxTokenLength: 131072,
440
+ maxReturnTokens: 4096
441
+ };
442
+
443
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
444
+
445
+ // Test vision message with multiple images
446
+ const visionMessages = [
447
+ {
448
+ role: 'user',
449
+ content: [
450
+ {
451
+ type: 'text',
452
+ text: 'What are in these images?'
453
+ },
454
+ {
455
+ type: 'image_url',
456
+ image_url: {
457
+ url: 'https://example.com/image1.jpg',
458
+ detail: 'high'
459
+ }
460
+ },
461
+ {
462
+ type: 'image_url',
463
+ image_url: {
464
+ url: 'https://example.com/image2.jpg',
465
+ detail: 'low'
466
+ }
467
+ },
468
+ {
469
+ type: 'text',
470
+ text: 'Compare them.'
471
+ }
472
+ ]
473
+ }
474
+ ];
475
+
476
+ const parameters = {};
477
+
478
+ const requestParams = await plugin.getRequestParameters('', parameters, {});
479
+ requestParams.messages = visionMessages;
480
+
481
+ // Verify multiple images message structure
482
+ t.is(requestParams.messages[0].content.length, 4, 'should have 4 content items');
483
+ t.is(requestParams.messages[0].content[0].type, 'text', 'first item should be text');
484
+ t.is(requestParams.messages[0].content[1].type, 'image_url', 'second item should be image_url');
485
+ t.is(requestParams.messages[0].content[2].type, 'image_url', 'third item should be image_url');
486
+ t.is(requestParams.messages[0].content[3].type, 'text', 'fourth item should be text');
487
+ t.is(requestParams.messages[0].content[1].image_url.detail, 'high', 'first image detail should be high');
488
+ t.is(requestParams.messages[0].content[2].image_url.detail, 'low', 'second image detail should be low');
489
+ });
490
+
491
+ test('should parse Grok response with citations', t => {
492
+ const mockPathway = {
493
+ name: 'test-pathway',
494
+ temperature: 0.7,
495
+ prompt: 'Test prompt'
496
+ };
497
+
498
+ const mockModel = {
499
+ name: 'xai-grok-4',
500
+ type: 'GROK-VISION',
501
+ url: 'https://api.x.ai/v1/chat/completions',
502
+ headers: {
503
+ 'Authorization': 'Bearer test-key',
504
+ 'Content-Type': 'application/json'
505
+ },
506
+ params: {
507
+ model: 'grok-4-0709'
508
+ },
509
+ maxTokenLength: 131072,
510
+ maxReturnTokens: 4096
511
+ };
512
+
513
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
514
+
515
+ const mockResponse = {
516
+ choices: [{
517
+ message: {
518
+ role: 'assistant',
519
+ content: 'Test response'
520
+ }
521
+ }],
522
+ citations: [
523
+ 'https://example.com'
524
+ ]
525
+ };
526
+
527
+ const result = plugin.parseResponse(mockResponse);
528
+
529
+ // Should return a CortexResponse object
530
+ t.is(result.output_text, 'Test response');
531
+ t.truthy(result.citations);
532
+ // Citations are created from URLs, so title is extracted from URL
533
+ t.is(result.citations[0].url, 'https://example.com');
534
+ });
535
+
536
+ test('should handle tool calls in response', t => {
537
+ const mockPathway = {
538
+ name: 'test-pathway',
539
+ temperature: 0.7,
540
+ prompt: 'Test prompt'
541
+ };
542
+
543
+ const mockModel = {
544
+ name: 'xai-grok-4',
545
+ type: 'GROK-VISION',
546
+ url: 'https://api.x.ai/v1/chat/completions',
547
+ headers: {
548
+ 'Authorization': 'Bearer test-key',
549
+ 'Content-Type': 'application/json'
550
+ },
551
+ params: {
552
+ model: 'grok-4-0709'
553
+ },
554
+ maxTokenLength: 131072,
555
+ maxReturnTokens: 4096
556
+ };
557
+
558
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
559
+
560
+ const mockResponse = {
561
+ choices: [{
562
+ message: {
563
+ role: 'assistant',
564
+ content: 'I will call a tool',
565
+ tool_calls: [
566
+ {
567
+ id: 'call_123',
568
+ type: 'function',
569
+ function: {
570
+ name: 'test_function',
571
+ arguments: '{"param": "value"}'
572
+ }
573
+ }
574
+ ]
575
+ }
576
+ }]
577
+ };
578
+
579
+ const result = plugin.parseResponse(mockResponse);
580
+
581
+ t.is(result.output_text, 'I will call a tool');
582
+ t.truthy(result.toolCalls);
583
+ t.is(result.toolCalls[0].function.name, 'test_function');
584
+ });
585
+
586
+ test('should handle string response from parent', t => {
587
+ const mockPathway = {
588
+ name: 'test-pathway',
589
+ temperature: 0.7,
590
+ prompt: 'Test prompt'
591
+ };
592
+
593
+ const mockModel = {
594
+ name: 'xai-grok-4',
595
+ type: 'GROK-VISION',
596
+ url: 'https://api.x.ai/v1/chat/completions',
597
+ headers: {
598
+ 'Authorization': 'Bearer test-key',
599
+ 'Content-Type': 'application/json'
600
+ },
601
+ params: {
602
+ model: 'grok-4-0709'
603
+ },
604
+ maxTokenLength: 131072,
605
+ maxReturnTokens: 4096
606
+ };
607
+
608
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
609
+
610
+ const mockResponse = {
611
+ choices: [{
612
+ message: {
613
+ role: 'assistant',
614
+ content: 'Simple text response'
615
+ }
616
+ }]
617
+ };
618
+
619
+ const result = plugin.parseResponse(mockResponse);
620
+
621
+ // Should return a CortexResponse object for simple responses
622
+ t.is(result.output_text, 'Simple text response');
623
+ });
624
+
625
+ test('should handle basic Grok API response format', t => {
626
+ const mockPathway = {
627
+ name: 'test-pathway',
628
+ temperature: 0.7,
629
+ prompt: 'Test prompt'
630
+ };
631
+
632
+ const mockModel = {
633
+ name: 'xai-grok-3',
634
+ type: 'GROK-VISION',
635
+ url: 'https://api.x.ai/v1/chat/completions',
636
+ headers: {
637
+ 'Authorization': 'Bearer test-key',
638
+ 'Content-Type': 'application/json'
639
+ },
640
+ params: {
641
+ model: 'grok-3-latest'
642
+ },
643
+ maxTokenLength: 131072,
644
+ maxReturnTokens: 4096
645
+ };
646
+
647
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
648
+
649
+ // This matches the actual Grok API response format from your curl example
650
+ const mockResponse = {
651
+ "id": "13493401-f153-7de6-ac07-c6f6b2609b06",
652
+ "object": "chat.completion",
653
+ "created": 1753113536,
654
+ "model": "grok-3",
655
+ "choices": [{
656
+ "index": 0,
657
+ "message": {
658
+ "role": "assistant",
659
+ "content": "Hi\nHello World",
660
+ "refusal": null
661
+ },
662
+ "finish_reason": "stop"
663
+ }],
664
+ "usage": {
665
+ "prompt_tokens": 28,
666
+ "completion_tokens": 4,
667
+ "total_tokens": 32,
668
+ "prompt_tokens_details": {
669
+ "text_tokens": 28,
670
+ "audio_tokens": 0,
671
+ "image_tokens": 0,
672
+ "cached_tokens": 5
673
+ },
674
+ "completion_tokens_details": {
675
+ "reasoning_tokens": 0,
676
+ "audio_tokens": 0,
677
+ "accepted_prediction_tokens": 0,
678
+ "rejected_prediction_tokens": 0
679
+ },
680
+ "num_sources_used": 0
681
+ },
682
+ "system_fingerprint": "fp_0d42a4eb3d"
683
+ };
684
+
685
+ const result = plugin.parseResponse(mockResponse);
686
+
687
+ // Should return a CortexResponse object
688
+ t.is(result.output_text, 'Hi\nHello World');
689
+ });
690
+
691
+ test('should parse messages with image content', async t => {
692
+ const mockPathway = {
693
+ name: 'test-pathway',
694
+ temperature: 0.7,
695
+ prompt: 'Test prompt'
696
+ };
697
+
698
+ const mockModel = {
699
+ name: 'xai-grok-4',
700
+ type: 'GROK-VISION',
701
+ url: 'https://api.x.ai/v1/chat/completions',
702
+ headers: {
703
+ 'Authorization': 'Bearer test-key',
704
+ 'Content-Type': 'application/json'
705
+ },
706
+ params: {
707
+ model: 'grok-4-0709'
708
+ },
709
+ maxTokenLength: 131072,
710
+ maxReturnTokens: 4096
711
+ };
712
+
713
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
714
+
715
+ const messages = [
716
+ {
717
+ role: 'user',
718
+ content: [
719
+ { type: 'text', text: 'Describe this image' },
720
+ {
721
+ type: 'image_url',
722
+ image_url: {
723
+ url: 'https://example.com/image.jpg',
724
+ detail: 'auto'
725
+ }
726
+ }
727
+ ]
728
+ }
729
+ ];
730
+
731
+ // Mock validateImageUrl to return true
732
+ plugin.validateImageUrl = () => Promise.resolve(true);
733
+
734
+ const result = await plugin.tryParseMessages(messages);
735
+
736
+ t.is(result[0].content.length, 2);
737
+ t.is(result[0].content[0].type, 'text');
738
+ t.is(result[0].content[1].type, 'image_url');
739
+ t.is(result[0].content[1].image_url.detail, 'auto');
740
+ });
741
+
742
+ test('should handle Grok vision response with web search results', t => {
743
+ const mockPathway = {
744
+ name: 'test-pathway',
745
+ temperature: 0.7,
746
+ prompt: 'Test prompt'
747
+ };
748
+
749
+ const mockModel = {
750
+ name: 'xai-grok-4',
751
+ type: 'GROK-VISION',
752
+ url: 'https://api.x.ai/v1/chat/completions',
753
+ headers: {
754
+ 'Authorization': 'Bearer test-key',
755
+ 'Content-Type': 'application/json'
756
+ },
757
+ params: {
758
+ model: 'grok-4-0709'
759
+ },
760
+ maxTokenLength: 131072,
761
+ maxReturnTokens: 4096
762
+ };
763
+
764
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
765
+
766
+ const mockResponse = {
767
+ choices: [{
768
+ message: {
769
+ role: 'assistant',
770
+ content: 'Based on the image and web search...'
771
+ }
772
+ }],
773
+ web_search_results: [
774
+ {
775
+ title: 'Search Result',
776
+ snippet: 'Relevant information',
777
+ url: 'https://example.com'
778
+ }
779
+ ]
780
+ };
781
+
782
+ const result = plugin.parseResponse(mockResponse);
783
+
784
+ // Should return a CortexResponse object
785
+ t.is(result.output_text, 'Based on the image and web search...');
786
+ t.truthy(result.searchResults);
787
+ t.is(result.searchResults[0].title, 'Search Result');
788
+ });
789
+
790
+ test('should handle streaming events with Grok-specific fields', t => {
791
+ const mockPathway = {
792
+ name: 'test-pathway',
793
+ temperature: 0.7,
794
+ prompt: 'Test prompt'
795
+ };
796
+
797
+ const mockModel = {
798
+ name: 'xai-grok-4',
799
+ type: 'GROK-VISION',
800
+ url: 'https://api.x.ai/v1/chat/completions',
801
+ headers: {
802
+ 'Authorization': 'Bearer test-key',
803
+ 'Content-Type': 'application/json'
804
+ },
805
+ params: {
806
+ model: 'grok-4-0709'
807
+ },
808
+ maxTokenLength: 131072,
809
+ maxReturnTokens: 4096
810
+ };
811
+
812
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
813
+
814
+ const event = {
815
+ data: JSON.stringify({
816
+ choices: [{
817
+ delta: {
818
+ content: 'Test content',
819
+ citations: [{ title: 'Citation', url: 'https://example.com' }],
820
+ search_queries: ['test query'],
821
+ web_search_results: [{ title: 'Result', url: 'https://example.com' }],
822
+ real_time_data: { timestamp: '2024-01-01T00:00:00Z', data: 'Real-time info' }
823
+ }
824
+ }]
825
+ })
826
+ };
827
+
828
+ const requestProgress = { data: '', progress: 0 };
829
+ const result = plugin.processStreamEvent(event, requestProgress);
830
+
831
+ // Should return the requestProgress object (calls parent implementation)
832
+ t.is(result, requestProgress);
833
+ t.is(typeof result.data, 'string');
834
+ t.is(typeof result.progress, 'number');
835
+ });
836
+
837
+ test('should handle end of stream event', t => {
838
+ const mockPathway = {
839
+ name: 'test-pathway',
840
+ temperature: 0.7,
841
+ prompt: 'Test prompt'
842
+ };
843
+
844
+ const mockModel = {
845
+ name: 'xai-grok-4',
846
+ type: 'GROK-VISION',
847
+ url: 'https://api.x.ai/v1/chat/completions',
848
+ headers: {
849
+ 'Authorization': 'Bearer test-key',
850
+ 'Content-Type': 'application/json'
851
+ },
852
+ params: {
853
+ model: 'grok-4-0709'
854
+ },
855
+ maxTokenLength: 131072,
856
+ maxReturnTokens: 4096
857
+ };
858
+
859
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
860
+
861
+ const event = { data: '[DONE]' };
862
+ const requestProgress = { data: '', progress: 0 };
863
+
864
+ const result = plugin.processStreamEvent(event, requestProgress);
865
+
866
+ t.is(result.progress, 1);
867
+ });
868
+
869
+ test('should handle Live Search parameters correctly', async t => {
870
+ const mockPathway = {
871
+ name: 'test-pathway',
872
+ temperature: 0.7,
873
+ prompt: 'Test prompt'
874
+ };
875
+
876
+ const mockModel = {
877
+ name: 'xai-grok-3',
878
+ type: 'GROK-VISION',
879
+ url: 'https://api.x.ai/v1/chat/completions',
880
+ headers: {
881
+ 'Authorization': 'Bearer test-key',
882
+ 'Content-Type': 'application/json'
883
+ },
884
+ params: {
885
+ model: 'grok-3-latest'
886
+ },
887
+ maxTokenLength: 131072,
888
+ maxReturnTokens: 4096
889
+ };
890
+
891
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
892
+
893
+ // Test Live Search specific parameters
894
+ const parameters = {
895
+ search_parameters: '{"mode": "auto"}'
896
+ };
897
+
898
+ const requestParams = await plugin.getRequestParameters('Search for latest tech news', parameters, {});
899
+
900
+ // Verify search_parameters are set correctly
901
+ t.truthy(requestParams.search_parameters, 'search_parameters should be set');
902
+ t.is(requestParams.search_parameters.mode, 'auto', 'search mode should be auto');
903
+ });
904
+
905
+ test('should parse Live Search response with real-time data', t => {
906
+ const mockPathway = {
907
+ name: 'test-pathway',
908
+ temperature: 0.7,
909
+ prompt: 'Test prompt'
910
+ };
911
+
912
+ const mockModel = {
913
+ name: 'xai-grok-3',
914
+ type: 'GROK-VISION',
915
+ url: 'https://api.x.ai/v1/chat/completions',
916
+ headers: {
917
+ 'Authorization': 'Bearer test-key',
918
+ 'Content-Type': 'application/json'
919
+ },
920
+ params: {
921
+ model: 'grok-3-latest'
922
+ },
923
+ maxTokenLength: 131072,
924
+ maxReturnTokens: 4096
925
+ };
926
+
927
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
928
+
929
+ // Mock response with Live Search data
930
+ const mockResponse = {
931
+ choices: [{
932
+ message: {
933
+ role: 'assistant',
934
+ content: 'Based on real-time data from X...'
935
+ }
936
+ }],
937
+ citations: [
938
+ 'https://x.com/user/status/123456'
939
+ ],
940
+ search_queries: ['AI trends', 'artificial intelligence 2024'],
941
+ web_search_results: [
942
+ {
943
+ title: 'Latest AI Developments',
944
+ snippet: 'Recent breakthroughs in AI technology...',
945
+ url: 'https://example.com/ai-news'
946
+ }
947
+ ],
948
+ real_time_data: {
949
+ timestamp: '2024-01-01T12:00:00Z',
950
+ platform: 'X',
951
+ trending_topics: ['AI', 'Machine Learning'],
952
+ engagement_metrics: {
953
+ likes: 1000,
954
+ retweets: 500,
955
+ replies: 200
956
+ }
957
+ }
958
+ };
959
+
960
+ const result = plugin.parseResponse(mockResponse);
961
+
962
+ // Verify all Live Search response fields are parsed correctly
963
+ t.is(result.output_text, 'Based on real-time data from X...');
964
+ t.truthy(result.citations, 'Should have citations');
965
+ t.is(result.citations[0].url, 'https://x.com/user/status/123456');
966
+ t.truthy(result.searchQueries, 'Should have search queries');
967
+ t.is(result.searchQueries[0], 'AI trends');
968
+ t.truthy(result.searchResults, 'Should have web search results');
969
+ t.is(result.searchResults[0].title, 'Latest AI Developments');
970
+ t.truthy(result.realTimeData, 'Should have real-time data');
971
+ t.is(result.realTimeData.platform, 'X');
972
+ t.truthy(result.realTimeData.trending_topics, 'Should have trending topics');
973
+ });
974
+
975
+ test('should parse Live Search response with usage data', t => {
976
+ const mockPathway = {
977
+ name: 'test-pathway',
978
+ temperature: 0.7,
979
+ prompt: 'Test prompt'
980
+ };
981
+
982
+ const mockModel = {
983
+ name: 'xai-grok-3',
984
+ type: 'GROK-VISION',
985
+ url: 'https://api.x.ai/v1/chat/completions',
986
+ headers: {
987
+ 'Authorization': 'Bearer test-key',
988
+ 'Content-Type': 'application/json'
989
+ },
990
+ params: {
991
+ model: 'grok-3-latest'
992
+ },
993
+ maxTokenLength: 131072,
994
+ maxReturnTokens: 4096
995
+ };
996
+
997
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
998
+
999
+ // Mock response with Live Search usage data
1000
+ const mockResponse = {
1001
+ choices: [{
1002
+ message: {
1003
+ role: 'assistant',
1004
+ content: 'Based on live search results...'
1005
+ }
1006
+ }],
1007
+ citations: [
1008
+ 'https://example.com/result1',
1009
+ 'https://example.com/result2'
1010
+ ],
1011
+ usage: {
1012
+ prompt_tokens: 150,
1013
+ completion_tokens: 200,
1014
+ total_tokens: 350,
1015
+ num_sources_used: 5
1016
+ }
1017
+ };
1018
+
1019
+ const result = plugin.parseResponse(mockResponse);
1020
+
1021
+ // Verify response content and citations
1022
+ t.is(result.output_text, 'Based on live search results...');
1023
+ t.truthy(result.citations, 'Should have citations');
1024
+ t.is(result.citations.length, 2, 'Should have 2 citations');
1025
+ t.is(result.citations[0].url, 'https://example.com/result1');
1026
+ t.is(result.citations[1].url, 'https://example.com/result2');
1027
+
1028
+ // Verify usage data is preserved
1029
+ t.truthy(mockResponse.usage, 'Usage data should be preserved in original response');
1030
+ t.is(mockResponse.usage.num_sources_used, 5, 'Should show 5 sources used');
1031
+ });
1032
+
1033
+ test('should validate search parameters - valid parameters', t => {
1034
+ const mockPathway = {
1035
+ name: 'test-pathway',
1036
+ temperature: 0.7,
1037
+ prompt: 'Test prompt'
1038
+ };
1039
+
1040
+ const mockModel = {
1041
+ name: 'xai-grok-3',
1042
+ type: 'GROK-VISION',
1043
+ url: 'https://api.x.ai/v1/chat/completions',
1044
+ headers: {
1045
+ 'Authorization': 'Bearer test-key',
1046
+ 'Content-Type': 'application/json'
1047
+ },
1048
+ params: {
1049
+ model: 'grok-3-latest'
1050
+ },
1051
+ maxTokenLength: 131072,
1052
+ maxReturnTokens: 4096
1053
+ };
1054
+
1055
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
1056
+
1057
+ const validParams = {
1058
+ mode: 'auto',
1059
+ return_citations: true,
1060
+ from_date: '2024-01-01',
1061
+ to_date: '2024-12-31',
1062
+ max_search_results: 25,
1063
+ sources: [
1064
+ { type: 'web', country: 'US' },
1065
+ { type: 'x', included_x_handles: ['testuser'] },
1066
+ { type: 'news' },
1067
+ { type: 'rss', links: ['https://example.com/feed.xml'] }
1068
+ ]
1069
+ };
1070
+
1071
+ t.true(plugin.validateSearchParameters(validParams));
1072
+ });
1073
+
1074
+ test('should validate search parameters - invalid mode', t => {
1075
+ const mockPathway = {
1076
+ name: 'test-pathway',
1077
+ temperature: 0.7,
1078
+ prompt: 'Test prompt'
1079
+ };
1080
+
1081
+ const mockModel = {
1082
+ name: 'xai-grok-3',
1083
+ type: 'GROK-VISION',
1084
+ url: 'https://api.x.ai/v1/chat/completions',
1085
+ headers: {
1086
+ 'Authorization': 'Bearer test-key',
1087
+ 'Content-Type': 'application/json'
1088
+ },
1089
+ params: {
1090
+ model: 'grok-3-latest'
1091
+ },
1092
+ maxTokenLength: 131072,
1093
+ maxReturnTokens: 4096
1094
+ };
1095
+
1096
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
1097
+
1098
+ const invalidParams = {
1099
+ mode: 'invalid_mode'
1100
+ };
1101
+
1102
+ const error = t.throws(() => plugin.validateSearchParameters(invalidParams));
1103
+ t.true(error.message.includes('Invalid \'mode\' parameter'));
1104
+ });
1105
+
1106
+ test('should validate search parameters - invalid date format', t => {
1107
+ const mockPathway = {
1108
+ name: 'test-pathway',
1109
+ temperature: 0.7,
1110
+ prompt: 'Test prompt'
1111
+ };
1112
+
1113
+ const mockModel = {
1114
+ name: 'xai-grok-3',
1115
+ type: 'GROK-VISION',
1116
+ url: 'https://api.x.ai/v1/chat/completions',
1117
+ headers: {
1118
+ 'Authorization': 'Bearer test-key',
1119
+ 'Content-Type': 'application/json'
1120
+ },
1121
+ params: {
1122
+ model: 'grok-3-latest'
1123
+ },
1124
+ maxTokenLength: 131072,
1125
+ maxReturnTokens: 4096
1126
+ };
1127
+
1128
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
1129
+
1130
+ const invalidParams = {
1131
+ from_date: '2024/01/01' // Invalid format
1132
+ };
1133
+
1134
+ const error = t.throws(() => plugin.validateSearchParameters(invalidParams));
1135
+ t.true(error.message.includes('must be in YYYY-MM-DD format'));
1136
+ });
1137
+
1138
+ test('should validate search parameters - invalid max_search_results', t => {
1139
+ const mockPathway = {
1140
+ name: 'test-pathway',
1141
+ temperature: 0.7,
1142
+ prompt: 'Test prompt'
1143
+ };
1144
+
1145
+ const mockModel = {
1146
+ name: 'xai-grok-3',
1147
+ type: 'GROK-VISION',
1148
+ url: 'https://api.x.ai/v1/chat/completions',
1149
+ headers: {
1150
+ 'Authorization': 'Bearer test-key',
1151
+ 'Content-Type': 'application/json'
1152
+ },
1153
+ params: {
1154
+ model: 'grok-3-latest'
1155
+ },
1156
+ maxTokenLength: 131072,
1157
+ maxReturnTokens: 4096
1158
+ };
1159
+
1160
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
1161
+
1162
+ const invalidParams = {
1163
+ max_search_results: 100 // Too high
1164
+ };
1165
+
1166
+ const error = t.throws(() => plugin.validateSearchParameters(invalidParams));
1167
+ t.true(error.message.includes('must be 50 or less'));
1168
+ });
1169
+
1170
+ test('should validate search parameters - invalid X handles count', t => {
1171
+ const mockPathway = {
1172
+ name: 'test-pathway',
1173
+ temperature: 0.7,
1174
+ prompt: 'Test prompt'
1175
+ };
1176
+
1177
+ const mockModel = {
1178
+ name: 'xai-grok-3',
1179
+ type: 'GROK-VISION',
1180
+ url: 'https://api.x.ai/v1/chat/completions',
1181
+ headers: {
1182
+ 'Authorization': 'Bearer test-key',
1183
+ 'Content-Type': 'application/json'
1184
+ },
1185
+ params: {
1186
+ model: 'grok-3-latest'
1187
+ },
1188
+ maxTokenLength: 131072,
1189
+ maxReturnTokens: 4096
1190
+ };
1191
+
1192
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
1193
+
1194
+ const invalidParams = {
1195
+ sources: [{
1196
+ type: 'x',
1197
+ included_x_handles: Array(11).fill('user') // Too many handles
1198
+ }]
1199
+ };
1200
+
1201
+ const error = t.throws(() => plugin.validateSearchParameters(invalidParams));
1202
+ t.true(error.message.includes('can have a maximum of 10 items'));
1203
+ });
1204
+
1205
+ test('should validate search parameters - conflicting X handles', t => {
1206
+ const mockPathway = {
1207
+ name: 'test-pathway',
1208
+ temperature: 0.7,
1209
+ prompt: 'Test prompt'
1210
+ };
1211
+
1212
+ const mockModel = {
1213
+ name: 'xai-grok-3',
1214
+ type: 'GROK-VISION',
1215
+ url: 'https://api.x.ai/v1/chat/completions',
1216
+ headers: {
1217
+ 'Authorization': 'Bearer test-key',
1218
+ 'Content-Type': 'application/json'
1219
+ },
1220
+ params: {
1221
+ model: 'grok-3-latest'
1222
+ },
1223
+ maxTokenLength: 131072,
1224
+ maxReturnTokens: 4096
1225
+ };
1226
+
1227
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
1228
+
1229
+ const invalidParams = {
1230
+ sources: [{
1231
+ type: 'x',
1232
+ included_x_handles: ['user1'],
1233
+ excluded_x_handles: ['user2'] // Cannot specify both
1234
+ }]
1235
+ };
1236
+
1237
+ const error = t.throws(() => plugin.validateSearchParameters(invalidParams));
1238
+ t.true(error.message.includes('cannot be specified simultaneously'));
1239
+ });
1240
+
1241
+ test('should validate search parameters - invalid RSS links count', t => {
1242
+ const mockPathway = {
1243
+ name: 'test-pathway',
1244
+ temperature: 0.7,
1245
+ prompt: 'Test prompt'
1246
+ };
1247
+
1248
+ const mockModel = {
1249
+ name: 'xai-grok-3',
1250
+ type: 'GROK-VISION',
1251
+ url: 'https://api.x.ai/v1/chat/completions',
1252
+ headers: {
1253
+ 'Authorization': 'Bearer test-key',
1254
+ 'Content-Type': 'application/json'
1255
+ },
1256
+ params: {
1257
+ model: 'grok-3-latest'
1258
+ },
1259
+ maxTokenLength: 131072,
1260
+ maxReturnTokens: 4096
1261
+ };
1262
+
1263
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
1264
+
1265
+ const invalidParams = {
1266
+ sources: [{
1267
+ type: 'rss',
1268
+ links: ['https://feed1.xml', 'https://feed2.xml'] // Too many links
1269
+ }]
1270
+ };
1271
+
1272
+ const error = t.throws(() => plugin.validateSearchParameters(invalidParams));
1273
+ t.true(error.message.includes('can only have one item'));
1274
+ });
1275
+
1276
+ test('safeJsonParse should parse valid JSON', t => {
1277
+ const mockPathway = {
1278
+ name: 'test-pathway',
1279
+ temperature: 0.7,
1280
+ prompt: 'Test prompt'
1281
+ };
1282
+
1283
+ const mockModel = {
1284
+ name: 'xai-grok-3',
1285
+ type: 'GROK-VISION',
1286
+ url: 'https://api.x.ai/v1/chat/completions',
1287
+ headers: {
1288
+ 'Authorization': 'Bearer test-key',
1289
+ 'Content-Type': 'application/json'
1290
+ },
1291
+ params: {
1292
+ model: 'grok-3-latest'
1293
+ },
1294
+ maxTokenLength: 131072,
1295
+ maxReturnTokens: 4096
1296
+ };
1297
+
1298
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
1299
+
1300
+ const validJson = '{"key": "value", "number": 42}';
1301
+ const result = safeJsonParse(validJson);
1302
+
1303
+ t.deepEqual(result, { key: 'value', number: 42 });
1304
+ });
1305
+
1306
+ test('safeJsonParse should return original string for invalid JSON', t => {
1307
+ const mockPathway = {
1308
+ name: 'test-pathway',
1309
+ temperature: 0.7,
1310
+ prompt: 'Test prompt'
1311
+ };
1312
+
1313
+ const mockModel = {
1314
+ name: 'xai-grok-3',
1315
+ type: 'GROK-VISION',
1316
+ url: 'https://api.x.ai/v1/chat/completions',
1317
+ headers: {
1318
+ 'Authorization': 'Bearer test-key',
1319
+ 'Content-Type': 'application/json'
1320
+ },
1321
+ params: {
1322
+ model: 'grok-3-latest'
1323
+ },
1324
+ maxTokenLength: 131072,
1325
+ maxReturnTokens: 4096
1326
+ };
1327
+
1328
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
1329
+
1330
+ const invalidJson = '{"key": "value", "invalid": }';
1331
+ const result = safeJsonParse(invalidJson);
1332
+
1333
+ t.is(result, invalidJson);
1334
+ });
1335
+
1336
+ test('safeJsonParse should return non-string input as-is', t => {
1337
+ const mockPathway = {
1338
+ name: 'test-pathway',
1339
+ temperature: 0.7,
1340
+ prompt: 'Test prompt'
1341
+ };
1342
+
1343
+ const mockModel = {
1344
+ name: 'xai-grok-3',
1345
+ type: 'GROK-VISION',
1346
+ url: 'https://api.x.ai/v1/chat/completions',
1347
+ headers: {
1348
+ 'Authorization': 'Bearer test-key',
1349
+ 'Content-Type': 'application/json'
1350
+ },
1351
+ params: {
1352
+ model: 'grok-3-latest'
1353
+ },
1354
+ maxTokenLength: 131072,
1355
+ maxReturnTokens: 4096
1356
+ };
1357
+
1358
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
1359
+
1360
+ const objectInput = { key: 'value' };
1361
+ const result = safeJsonParse(objectInput);
1362
+
1363
+ t.is(result, objectInput);
1364
+ });
1365
+
1366
+ test('safeJsonParse should return null/undefined as-is', t => {
1367
+ const mockPathway = {
1368
+ name: 'test-pathway',
1369
+ temperature: 0.7,
1370
+ prompt: 'Test prompt'
1371
+ };
1372
+
1373
+ const mockModel = {
1374
+ name: 'xai-grok-3',
1375
+ type: 'GROK-VISION',
1376
+ url: 'https://api.x.ai/v1/chat/completions',
1377
+ headers: {
1378
+ 'Authorization': 'Bearer test-key',
1379
+ 'Content-Type': 'application/json'
1380
+ },
1381
+ params: {
1382
+ model: 'grok-3-latest'
1383
+ },
1384
+ maxTokenLength: 131072,
1385
+ maxReturnTokens: 4096
1386
+ };
1387
+
1388
+ const plugin = new GrokVisionPlugin(mockPathway, mockModel);
1389
+
1390
+ t.is(safeJsonParse(null), null);
1391
+ t.is(safeJsonParse(undefined), undefined);
1392
+ });