@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
@@ -1,6 +1,7 @@
1
1
  import test from "ava";
2
2
  import { StorageService } from "../../src/services/storage/StorageService.js";
3
3
  import { StorageFactory } from "../../src/services/storage/StorageFactory.js";
4
+ import { getFileStoreMap, setFileStoreMap, removeFromFileStoreMap } from "../../src/redis.js";
4
5
  import path from "path";
5
6
  import os from "os";
6
7
  import fs from "fs";
@@ -46,20 +47,30 @@ test("should upload file to primary storage", async (t) => {
46
47
  test("should upload file to backup storage", async (t) => {
47
48
  const factory = new StorageFactory();
48
49
  const service = new StorageService(factory);
49
- const provider = service.getBackupProvider();
50
+ const provider = await service.getBackupProvider();
50
51
  if (!provider) {
51
- t.log("GCS not configured, skipping test");
52
+ t.log("Backup provider not configured, skipping test");
52
53
  t.pass();
53
54
  return;
54
55
  }
55
- const testContent = "test content";
56
- const buffer = Buffer.from(testContent);
56
+
57
+ try {
58
+ const testContent = "test content";
59
+ const buffer = Buffer.from(testContent);
57
60
 
58
- const result = await service.uploadFileToBackup(buffer, "test.txt");
59
- t.truthy(result.url);
61
+ const result = await service.uploadFileToBackup(buffer, "test.txt");
62
+ t.truthy(result.url);
60
63
 
61
- // Cleanup
62
- await service.deleteFileFromBackup(result.url);
64
+ // Cleanup
65
+ await service.deleteFileFromBackup(result.url);
66
+ } catch (error) {
67
+ if (error.message === "Backup provider not configured") {
68
+ t.log("Backup provider not configured, skipping test");
69
+ t.pass();
70
+ } else {
71
+ throw error;
72
+ }
73
+ }
63
74
  });
64
75
 
65
76
  test("should download file from primary storage", async (t) => {
@@ -82,32 +93,387 @@ test("should download file from primary storage", async (t) => {
82
93
  test("should download file from backup storage", async (t) => {
83
94
  const factory = new StorageFactory();
84
95
  const service = new StorageService(factory);
85
- const provider = service.getBackupProvider();
96
+ const provider = await service.getBackupProvider();
86
97
  if (!provider) {
87
- t.log("GCS not configured, skipping test");
98
+ t.log("Backup provider not configured, skipping test");
88
99
  t.pass();
89
100
  return;
90
101
  }
91
- const testContent = "test content";
102
+
103
+ try {
104
+ const testContent = "test content";
105
+ const buffer = Buffer.from(testContent);
106
+
107
+ // Upload first
108
+ const uploadResult = await service.uploadFileToBackup(buffer, "test.txt");
109
+
110
+ // Create temp file for download
111
+ const tempFile = path.join(os.tmpdir(), "test-download.txt");
112
+ try {
113
+ // Download
114
+ await service.downloadFileFromBackup(uploadResult.url, tempFile);
115
+ const downloadedContent = await fs.promises.readFile(tempFile);
116
+ t.deepEqual(downloadedContent, buffer);
117
+
118
+ // Cleanup
119
+ await service.deleteFileFromBackup(uploadResult.url);
120
+ } finally {
121
+ // Cleanup temp file
122
+ if (fs.existsSync(tempFile)) {
123
+ fs.unlinkSync(tempFile);
124
+ }
125
+ }
126
+ } catch (error) {
127
+ if (error.message === "Backup provider not configured") {
128
+ t.log("Backup provider not configured, skipping test");
129
+ t.pass();
130
+ } else {
131
+ throw error;
132
+ }
133
+ }
134
+ });
135
+
136
+ test("should delete file by hash", async (t) => {
137
+ const factory = new StorageFactory();
138
+ const service = new StorageService(factory);
139
+ const testContent = "test content for hash deletion";
92
140
  const buffer = Buffer.from(testContent);
141
+ const testHash = "test-hash-123";
93
142
 
94
- // Upload first
95
- const uploadResult = await service.uploadFileToBackup(buffer, "test.txt");
143
+ try {
144
+ // Upload file first
145
+ const uploadResult = await service.uploadFile(buffer, "test-hash-delete.txt");
146
+ t.truthy(uploadResult.url);
147
+
148
+ // Store file info in Redis map
149
+ const fileInfo = {
150
+ url: uploadResult.url,
151
+ filename: "test-hash-delete.txt",
152
+ hash: testHash,
153
+ timestamp: new Date().toISOString()
154
+ };
155
+ await setFileStoreMap(testHash, fileInfo);
156
+
157
+ // Verify file exists in map
158
+ const storedInfo = await getFileStoreMap(testHash);
159
+ t.truthy(storedInfo);
160
+ t.is(storedInfo.url, uploadResult.url);
161
+
162
+ // Delete file by hash
163
+ const deleteResult = await service.deleteFileByHash(testHash);
164
+ t.truthy(deleteResult);
165
+ t.is(deleteResult.hash, testHash);
166
+ t.is(deleteResult.filename, "test-hash-delete.txt");
167
+ t.truthy(deleteResult.deleted);
168
+ t.true(Array.isArray(deleteResult.deleted));
169
+
170
+ // Verify file is removed from Redis map
171
+ const removedInfo = await getFileStoreMap(testHash);
172
+ t.falsy(removedInfo);
173
+
174
+ } catch (error) {
175
+ // Cleanup in case of error
176
+ try {
177
+ await removeFromFileStoreMap(testHash);
178
+ } catch (cleanupError) {
179
+ // Ignore cleanup errors
180
+ }
181
+ throw error;
182
+ }
183
+ });
184
+
185
+ test("should handle delete file by hash when file not found", async (t) => {
186
+ const factory = new StorageFactory();
187
+ const service = new StorageService(factory);
188
+ const nonExistentHash = "non-existent-hash-456";
189
+
190
+ try {
191
+ await service.deleteFileByHash(nonExistentHash);
192
+ t.fail("Should have thrown an error for non-existent hash");
193
+ } catch (error) {
194
+ t.true(error.message.includes("not found"));
195
+ }
196
+ });
197
+
198
+ test("should handle delete file by hash with missing hash parameter", async (t) => {
199
+ const factory = new StorageFactory();
200
+ const service = new StorageService(factory);
201
+
202
+ try {
203
+ await service.deleteFileByHash("");
204
+ t.fail("Should have thrown an error for empty hash");
205
+ } catch (error) {
206
+ t.true(error.message.includes("Missing hash parameter"));
207
+ }
208
+
209
+ try {
210
+ await service.deleteFileByHash(null);
211
+ t.fail("Should have thrown an error for null hash");
212
+ } catch (error) {
213
+ t.true(error.message.includes("Missing hash parameter"));
214
+ }
215
+
216
+ try {
217
+ await service.deleteFileByHash(undefined);
218
+ t.fail("Should have thrown an error for undefined hash");
219
+ } catch (error) {
220
+ t.true(error.message.includes("Missing hash parameter"));
221
+ }
222
+ });
223
+
224
+ test("should delete file by hash with backup storage", async (t) => {
225
+ const factory = new StorageFactory();
226
+ const service = new StorageService(factory);
227
+ const testContent = "test content for backup deletion";
228
+ const buffer = Buffer.from(testContent);
229
+ const testHash = "test-hash-backup-456";
230
+
231
+ try {
232
+ // Upload file first
233
+ const uploadResult = await service.uploadFile(buffer, "test-backup-delete.txt");
234
+ t.truthy(uploadResult.url);
235
+
236
+ // Store file info in Redis map with backup URL
237
+ const fileInfo = {
238
+ url: uploadResult.url,
239
+ gcs: "gs://test-bucket/test-backup-file.txt", // Mock backup URL
240
+ filename: "test-backup-delete.txt",
241
+ hash: testHash,
242
+ timestamp: new Date().toISOString()
243
+ };
244
+ await setFileStoreMap(testHash, fileInfo);
245
+
246
+ // Delete file by hash
247
+ const deleteResult = await service.deleteFileByHash(testHash);
248
+ t.truthy(deleteResult);
249
+ t.is(deleteResult.hash, testHash);
250
+ t.is(deleteResult.filename, "test-backup-delete.txt");
251
+ t.truthy(deleteResult.deleted);
252
+ t.true(Array.isArray(deleteResult.deleted));
253
+
254
+ // Should have attempted both primary and backup deletion
255
+ const deletionResults = deleteResult.deleted;
256
+ t.true(deletionResults.length >= 1, "Should have at least primary deletion result");
257
+
258
+ // Verify file is removed from Redis map
259
+ const removedInfo = await getFileStoreMap(testHash);
260
+ t.falsy(removedInfo);
261
+
262
+ } catch (error) {
263
+ // Cleanup in case of error
264
+ try {
265
+ await removeFromFileStoreMap(testHash);
266
+ } catch (cleanupError) {
267
+ // Ignore cleanup errors
268
+ }
269
+ throw error;
270
+ }
271
+ });
272
+
273
+ test("should handle delete file by hash when Redis map is corrupted", async (t) => {
274
+ const factory = new StorageFactory();
275
+ const service = new StorageService(factory);
276
+ const testHash = "test-hash-corrupted-789";
277
+
278
+ try {
279
+ // Store corrupted data in Redis map
280
+ const corruptedInfo = {
281
+ // Missing required fields like url
282
+ corrupted: true,
283
+ timestamp: new Date().toISOString()
284
+ };
285
+ await setFileStoreMap(testHash, corruptedInfo);
286
+
287
+ // Delete file by hash should handle corrupted data gracefully
288
+ const deleteResult = await service.deleteFileByHash(testHash);
289
+ t.truthy(deleteResult);
290
+ t.is(deleteResult.hash, testHash);
291
+ t.truthy(deleteResult.deleted);
292
+ t.true(Array.isArray(deleteResult.deleted));
293
+
294
+ // Should have removed the corrupted entry from Redis
295
+ const removedInfo = await getFileStoreMap(testHash);
296
+ t.falsy(removedInfo);
297
+
298
+ } catch (error) {
299
+ // Cleanup in case of error
300
+ try {
301
+ await removeFromFileStoreMap(testHash);
302
+ } catch (cleanupError) {
303
+ // Ignore cleanup errors
304
+ }
305
+ throw error;
306
+ }
307
+ });
308
+
309
+ test("should handle delete file by hash with empty URL in Redis", async (t) => {
310
+ const factory = new StorageFactory();
311
+ const service = new StorageService(factory);
312
+ const testHash = "test-hash-empty-url-654";
96
313
 
97
- // Create temp file for download
98
- const tempFile = path.join(os.tmpdir(), "test-download.txt");
99
314
  try {
100
- // Download
101
- await service.downloadFileFromBackup(uploadResult.url, tempFile);
102
- const downloadedContent = await fs.promises.readFile(tempFile);
103
- t.deepEqual(downloadedContent, buffer);
315
+ // Store file info in Redis map with empty/null URL
316
+ const fileInfo = {
317
+ url: null,
318
+ filename: "test-empty-url-delete.txt",
319
+ hash: testHash,
320
+ timestamp: new Date().toISOString()
321
+ };
322
+ await setFileStoreMap(testHash, fileInfo);
323
+
324
+ // Verify the hash exists in Redis before deletion (skip lazy cleanup)
325
+ const storedInfo = await getFileStoreMap(testHash, true);
326
+ t.truthy(storedInfo, "Hash should exist in Redis before deletion");
327
+
328
+ // Delete file by hash - should handle missing URL gracefully
329
+ const deleteResult = await service.deleteFileByHash(testHash);
330
+ t.truthy(deleteResult);
331
+ t.is(deleteResult.hash, testHash);
332
+ t.is(deleteResult.filename, "test-empty-url-delete.txt");
333
+ t.truthy(deleteResult.deleted);
334
+ t.true(Array.isArray(deleteResult.deleted));
335
+
336
+ // Should still remove from Redis map even if no actual file to delete
337
+ const removedInfo = await getFileStoreMap(testHash);
338
+ t.falsy(removedInfo);
339
+
340
+ } catch (error) {
341
+ // Cleanup in case of error
342
+ try {
343
+ await removeFromFileStoreMap(testHash);
344
+ } catch (cleanupError) {
345
+ // Ignore cleanup errors
346
+ }
347
+ throw error;
348
+ }
349
+ });
104
350
 
351
+ // Container-specific tests
352
+ test("should upload file with specific container name", async (t) => {
353
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
354
+ t.pass("Skipping test - Azure not configured");
355
+ return;
356
+ }
357
+
358
+ const factory = new StorageFactory();
359
+ const service = new StorageService(factory);
360
+
361
+ // Create a temporary file
362
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-"));
363
+ const testFile = path.join(tempDir, "test.txt");
364
+ fs.writeFileSync(testFile, "test content");
365
+
366
+ try {
367
+ // Mock environment to have multiple containers
368
+ const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
369
+ process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
370
+
371
+ try {
372
+ // Test upload with specific container
373
+ const result = await service.uploadFileWithProviders(
374
+ { log: () => {} }, // mock context
375
+ testFile,
376
+ "test-request",
377
+ null,
378
+ "test2"
379
+ );
380
+
381
+ t.truthy(result.url);
382
+ t.truthy(result.url.includes("test2") || result.url.includes("/test2/"));
383
+
384
+ // Cleanup
385
+ await service.deleteFiles("test-request");
386
+ } finally {
387
+ // Restore original env
388
+ if (originalEnv) {
389
+ process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
390
+ } else {
391
+ delete process.env.AZURE_STORAGE_CONTAINER_NAME;
392
+ }
393
+ }
394
+ } finally {
395
+ // Cleanup temp file
396
+ fs.rmSync(tempDir, { recursive: true, force: true });
397
+ }
398
+ });
399
+
400
+ test("should use default container when no container specified", async (t) => {
401
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
402
+ t.pass("Skipping test - Azure not configured");
403
+ return;
404
+ }
405
+
406
+ const factory = new StorageFactory();
407
+ const service = new StorageService(factory);
408
+
409
+ // Create a temporary file
410
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-"));
411
+ const testFile = path.join(tempDir, "test.txt");
412
+ fs.writeFileSync(testFile, "test content");
413
+
414
+ try {
415
+ // Test upload without container (should use default)
416
+ const result = await service.uploadFileWithProviders(
417
+ { log: () => {} }, // mock context
418
+ testFile,
419
+ "test-request",
420
+ null,
421
+ null // no container specified
422
+ );
423
+
424
+ t.truthy(result.url);
425
+
105
426
  // Cleanup
106
- await service.deleteFileFromBackup(uploadResult.url);
427
+ await service.deleteFiles("test-request");
107
428
  } finally {
108
429
  // Cleanup temp file
109
- if (fs.existsSync(tempFile)) {
110
- fs.unlinkSync(tempFile);
430
+ fs.rmSync(tempDir, { recursive: true, force: true });
431
+ }
432
+ });
433
+
434
+ test("should pass container parameter through uploadFile method", async (t) => {
435
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
436
+ t.pass("Skipping test - Azure not configured");
437
+ return;
438
+ }
439
+
440
+ const factory = new StorageFactory();
441
+ const service = new StorageService(factory);
442
+
443
+ // Create a temporary file
444
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-"));
445
+ const testFile = path.join(tempDir, "test.txt");
446
+ fs.writeFileSync(testFile, "test content");
447
+
448
+ try {
449
+ // Mock environment to have multiple containers
450
+ const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
451
+ process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
452
+
453
+ try {
454
+ // Test upload using the uploadFile method with container parameter
455
+ const result = await service.uploadFile(
456
+ { log: () => {} }, // context
457
+ testFile, // filePath
458
+ "test-request", // requestId
459
+ null, // hash
460
+ "test3" // containerName
461
+ );
462
+
463
+ t.truthy(result.url);
464
+
465
+ // Cleanup
466
+ await service.deleteFiles("test-request");
467
+ } finally {
468
+ // Restore original env
469
+ if (originalEnv) {
470
+ process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
471
+ } else {
472
+ delete process.env.AZURE_STORAGE_CONTAINER_NAME;
473
+ }
111
474
  }
475
+ } finally {
476
+ // Cleanup temp file
477
+ fs.rmSync(tempDir, { recursive: true, force: true });
112
478
  }
113
479
  });
@@ -1,6 +1,9 @@
1
1
  import axios from "axios";
2
2
  import { execSync } from "child_process";
3
3
  import fs from "fs/promises";
4
+ import path from "path";
5
+
6
+ import { app, port } from "../src/start.js";
4
7
 
5
8
  export async function cleanupHashAndFile(hash, uploadedUrl, baseUrl) {
6
9
  // Only perform hash operations if hash is provided
@@ -87,3 +90,74 @@ export async function createTestMediaFile(filepath, durationSeconds = 10) {
87
90
  throw error;
88
91
  }
89
92
  }
93
+
94
+ // Test server management helpers
95
+ let server;
96
+
97
+ /**
98
+ * Starts the test server and waits for it to be ready
99
+ * @param {Object} options - Optional configuration
100
+ * @param {Function} options.beforeReady - Optional callback to run after server starts but before ready check
101
+ * @returns {Promise<Object>} - Server instance
102
+ */
103
+ export async function startTestServer(options = {}) {
104
+ if (server) {
105
+ throw new Error("Test server is already running");
106
+ }
107
+
108
+ // Start the server for tests
109
+ server = app.listen(port, () => {
110
+ console.log(`Test server started on port ${port}`);
111
+ });
112
+
113
+ // Wait for server to be ready
114
+ await new Promise((resolve) => setTimeout(resolve, 1000));
115
+
116
+ // Run any pre-ready setup if provided
117
+ if (options.beforeReady) {
118
+ await options.beforeReady();
119
+ }
120
+
121
+ // Verify server is responding
122
+ try {
123
+ await axios.get(`http://localhost:${port}/files`);
124
+ } catch (error) {
125
+ // 404 is fine, it means server is running but directory is empty
126
+ if (error.response?.status !== 404) {
127
+ throw new Error("Server not ready");
128
+ }
129
+ }
130
+
131
+ return server;
132
+ }
133
+
134
+ /**
135
+ * Stops the test server
136
+ * @param {Function} beforeClose - Optional callback to run before closing server
137
+ * @returns {Promise<void>}
138
+ */
139
+ export async function stopTestServer(beforeClose) {
140
+ if (beforeClose) {
141
+ await beforeClose();
142
+ }
143
+
144
+ if (server) {
145
+ server.close();
146
+ server = null;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Creates a test directory and sets up context for AVA tests
152
+ * @param {Object} t - AVA test context
153
+ * @param {string} dirName - Optional directory name (defaults to "test-files")
154
+ * @returns {Promise<string>} - Path to created test directory
155
+ */
156
+ export async function setupTestDirectory(t, dirName = "test-files") {
157
+ const testDir = path.join(process.cwd(), "tests", dirName);
158
+ await fs.mkdir(testDir, { recursive: true });
159
+ if (t && t.context) {
160
+ t.context.testDir = testDir;
161
+ }
162
+ return testDir;
163
+ }
@@ -0,0 +1,153 @@
1
+ class CortexResponse {
2
+ constructor({
3
+ output_text = "",
4
+ output = null,
5
+ finishReason = 'stop',
6
+ toolCalls = null,
7
+ functionCall = null,
8
+ citations = null,
9
+ searchQueries = null,
10
+ searchResults = null,
11
+ realTimeData = null,
12
+ artifacts = null,
13
+ usage = null,
14
+ metadata = {},
15
+ error = null
16
+ } = {}) {
17
+ this._output_text = output_text;
18
+ this._output = output;
19
+ this._finishReason = finishReason;
20
+ this._toolCalls = toolCalls;
21
+ this._functionCall = functionCall;
22
+ this._citations = citations;
23
+ this._searchQueries = searchQueries;
24
+ this._searchResults = searchResults;
25
+ this._realTimeData = realTimeData;
26
+ this._artifacts = artifacts;
27
+ this._usage = usage;
28
+ this._metadata = {
29
+ timestamp: new Date().toISOString(),
30
+ ...metadata
31
+ };
32
+ this._error = error;
33
+ }
34
+
35
+ // Getters
36
+ get output_text() { return this._output_text; }
37
+ get output() { return this._output; }
38
+ get finishReason() { return this._finishReason; }
39
+ get toolCalls() { return this._toolCalls; }
40
+ get tool_calls() { return this._toolCalls; } // For legacy compatibility
41
+ get functionCall() { return this._functionCall; }
42
+ get citations() { return this._citations; }
43
+ get searchQueries() { return this._searchQueries; }
44
+ get searchResults() { return this._searchResults; }
45
+ get realTimeData() { return this._realTimeData; }
46
+ get artifacts() { return this._artifacts; }
47
+ get usage() { return this._usage; }
48
+ get metadata() { return this._metadata; }
49
+ get error() { return this._error; }
50
+
51
+ // Setters
52
+ set output_text(value) { this._output_text = value; }
53
+ set output(value) { this._output = value; }
54
+ set finishReason(value) { this._finishReason = value; }
55
+ set toolCalls(value) {
56
+ this._toolCalls = value;
57
+ if (value && value.length > 0) {
58
+ this._finishReason = 'tool_calls';
59
+ }
60
+ }
61
+ set functionCall(value) {
62
+ this._functionCall = value;
63
+ if (value) {
64
+ this._finishReason = 'function_call';
65
+ }
66
+ }
67
+ set citations(value) { this._citations = value; }
68
+ set searchQueries(value) { this._searchQueries = value; }
69
+ set searchResults(value) { this._searchResults = value; }
70
+ set realTimeData(value) { this._realTimeData = value; }
71
+ set artifacts(value) { this._artifacts = value; }
72
+ set usage(value) { this._usage = value; }
73
+ set metadata(value) { this._metadata = { ...this._metadata, ...value }; }
74
+ set error(value) { this._error = value; }
75
+
76
+ // Utility methods
77
+ hasToolCalls() {
78
+ return this._toolCalls && this._toolCalls.length > 0;
79
+ }
80
+
81
+ hasCitations() {
82
+ return this._citations && this._citations.length > 0;
83
+ }
84
+
85
+ hasSearchResults() {
86
+ return this._searchResults && this._searchResults.length > 0;
87
+ }
88
+
89
+ hasArtifacts() {
90
+ return this._artifacts && this._artifacts.length > 0;
91
+ }
92
+
93
+ isError() {
94
+ return this._error !== null;
95
+ }
96
+
97
+ hasOutput() {
98
+ return this._output && this._output.length > 0;
99
+ }
100
+
101
+ hasOutputText() {
102
+ return this._output_text && this._output_text.length > 0;
103
+ }
104
+
105
+ // Get text content from output array (OpenAI format)
106
+ getTextFromOutput() {
107
+ if (!this._output || !Array.isArray(this._output)) {
108
+ return this._output_text || "";
109
+ }
110
+
111
+ // Extract text from output items that have text content
112
+ const textItems = this._output
113
+ .filter(item => item && item.text)
114
+ .map(item => item.text);
115
+
116
+ return textItems.join("");
117
+ }
118
+
119
+
120
+ // String compatibility methods
121
+ toString() {
122
+ return this._output_text;
123
+ }
124
+
125
+ valueOf() {
126
+ return this._output_text;
127
+ }
128
+
129
+ toJSON() {
130
+ return this._output_text;
131
+ }
132
+
133
+ // Convert to plain object
134
+ toObject() {
135
+ return {
136
+ output_text: this._output_text,
137
+ output: this._output,
138
+ finishReason: this._finishReason,
139
+ toolCalls: this._toolCalls,
140
+ functionCall: this._functionCall,
141
+ citations: this._citations,
142
+ searchQueries: this._searchQueries,
143
+ searchResults: this._searchResults,
144
+ realTimeData: this._realTimeData,
145
+ artifacts: this._artifacts,
146
+ usage: this._usage,
147
+ metadata: this._metadata,
148
+ error: this._error
149
+ };
150
+ }
151
+ }
152
+
153
+ export default CortexResponse;