@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
@@ -21,7 +21,7 @@ import {
21
21
  generateBlobName,
22
22
  } from "./utils/filenameUtils.js";
23
23
  import { publicFolder, port, ipAddress } from "./start.js";
24
- import { CONVERTED_EXTENSIONS } from "./constants.js";
24
+ import { CONVERTED_EXTENSIONS, AZURITE_ACCOUNT_NAME } from "./constants.js";
25
25
  import { FileConversionService } from "./services/FileConversionService.js";
26
26
 
27
27
  const pipeline = promisify(_pipeline);
@@ -35,14 +35,23 @@ function isBase64(str) {
35
35
  }
36
36
 
37
37
  const { SAS_TOKEN_LIFE_DAYS = 30 } = process.env;
38
- const GCP_SERVICE_ACCOUNT_KEY =
39
- process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64 ||
40
- process.env.GCP_SERVICE_ACCOUNT_KEY ||
41
- "{}";
42
- const GCP_SERVICE_ACCOUNT = isBase64(GCP_SERVICE_ACCOUNT_KEY)
43
- ? JSON.parse(Buffer.from(GCP_SERVICE_ACCOUNT_KEY, "base64").toString())
44
- : JSON.parse(GCP_SERVICE_ACCOUNT_KEY);
45
- const { project_id: GCP_PROJECT_ID } = GCP_SERVICE_ACCOUNT;
38
+ let GCP_SERVICE_ACCOUNT;
39
+ let GCP_PROJECT_ID;
40
+
41
+ try {
42
+ const GCP_SERVICE_ACCOUNT_KEY =
43
+ process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64 ||
44
+ process.env.GCP_SERVICE_ACCOUNT_KEY ||
45
+ "{}";
46
+ GCP_SERVICE_ACCOUNT = isBase64(GCP_SERVICE_ACCOUNT_KEY)
47
+ ? JSON.parse(Buffer.from(GCP_SERVICE_ACCOUNT_KEY, "base64").toString())
48
+ : JSON.parse(GCP_SERVICE_ACCOUNT_KEY);
49
+ GCP_PROJECT_ID = GCP_SERVICE_ACCOUNT.project_id;
50
+ } catch (error) {
51
+ console.warn("Error parsing GCP service account credentials, GCS will not be used:", error.message);
52
+ GCP_SERVICE_ACCOUNT = {};
53
+ GCP_PROJECT_ID = null;
54
+ }
46
55
 
47
56
  let gcs;
48
57
  if (!GCP_PROJECT_ID || !GCP_SERVICE_ACCOUNT) {
@@ -65,10 +74,21 @@ if (!GCP_PROJECT_ID || !GCP_SERVICE_ACCOUNT) {
65
74
  }
66
75
  }
67
76
 
68
- export const AZURE_STORAGE_CONTAINER_NAME =
69
- process.env.AZURE_STORAGE_CONTAINER_NAME || "whispertempfiles";
77
+ // Parse comma-separated container names from environment variable
78
+ const parseContainerNames = () => {
79
+ const containerStr = process.env.AZURE_STORAGE_CONTAINER_NAME || "whispertempfiles";
80
+ return containerStr.split(',').map(name => name.trim());
81
+ };
82
+
83
+ export const AZURE_STORAGE_CONTAINER_NAMES = parseContainerNames();
84
+ export const DEFAULT_AZURE_STORAGE_CONTAINER_NAME = AZURE_STORAGE_CONTAINER_NAMES[0];
70
85
  export const GCS_BUCKETNAME = process.env.GCS_BUCKETNAME || "cortextempfiles";
71
86
 
87
+ // Validate if a container name is allowed
88
+ export const isValidContainerName = (containerName) => {
89
+ return AZURE_STORAGE_CONTAINER_NAMES.includes(containerName);
90
+ };
91
+
72
92
  function isEncoded(str) {
73
93
  // Checks for any percent-encoded sequence
74
94
  return /%[0-9A-Fa-f]{2}/.test(str);
@@ -169,10 +189,18 @@ async function downloadFromGCS(gcsUrl, destinationPath) {
169
189
  }
170
190
  }
171
191
 
172
- export const getBlobClient = async () => {
192
+ export const getBlobClient = async (containerName = null) => {
173
193
  const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
174
- const containerName = AZURE_STORAGE_CONTAINER_NAME;
175
- if (!connectionString || !containerName) {
194
+ const finalContainerName = containerName || DEFAULT_AZURE_STORAGE_CONTAINER_NAME;
195
+
196
+ // Validate container name is in whitelist
197
+ if (!isValidContainerName(finalContainerName)) {
198
+ throw new Error(
199
+ `Invalid container name '${finalContainerName}'. Allowed containers: ${AZURE_STORAGE_CONTAINER_NAMES.join(', ')}`,
200
+ );
201
+ }
202
+
203
+ if (!connectionString || !finalContainerName) {
176
204
  throw new Error(
177
205
  "Missing Azure Storage connection string or container name environment variable",
178
206
  );
@@ -187,13 +215,13 @@ export const getBlobClient = async () => {
187
215
  await blobServiceClient.setProperties(serviceProperties);
188
216
  }
189
217
 
190
- const containerClient = blobServiceClient.getContainerClient(containerName);
218
+ const containerClient = blobServiceClient.getContainerClient(finalContainerName);
191
219
 
192
220
  return { blobServiceClient, containerClient };
193
221
  };
194
222
 
195
- async function saveFileToBlob(chunkPath, requestId, filename = null) {
196
- const { containerClient } = await getBlobClient();
223
+ async function saveFileToBlob(chunkPath, requestId, filename = null, containerName = null) {
224
+ const { containerClient } = await getBlobClient(containerName);
197
225
  // Use provided filename or generate LLM-friendly naming
198
226
  let blobName;
199
227
  if (filename) {
@@ -224,20 +252,55 @@ async function saveFileToBlob(chunkPath, requestId, filename = null) {
224
252
  const generateSASToken = (
225
253
  containerClient,
226
254
  blobName,
227
- expiryTimeSeconds = parseInt(SAS_TOKEN_LIFE_DAYS) * 24 * 60 * 60,
255
+ options = {},
228
256
  ) => {
229
- const { accountName, accountKey } = containerClient.credential;
257
+ // Handle Azurite (development storage) credentials with fallback
258
+ let accountName, accountKey;
259
+
260
+ if (containerClient.credential && containerClient.credential.accountName) {
261
+ // Regular Azure Storage credentials
262
+ accountName = containerClient.credential.accountName;
263
+
264
+ // Handle Buffer case (Azurite) vs string case (real Azure)
265
+ if (Buffer.isBuffer(containerClient.credential.accountKey)) {
266
+ accountKey = containerClient.credential.accountKey.toString('base64');
267
+ } else {
268
+ accountKey = containerClient.credential.accountKey;
269
+ }
270
+ } else {
271
+ // Azurite development storage fallback
272
+ accountName = AZURITE_ACCOUNT_NAME;
273
+ accountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
274
+ }
275
+
230
276
  const sharedKeyCredential = new StorageSharedKeyCredential(
231
277
  accountName,
232
278
  accountKey,
233
279
  );
234
280
 
281
+ // Support custom duration: minutes, hours, or fall back to default
282
+ let expirationTime;
283
+ if (options.minutes) {
284
+ expirationTime = new Date(new Date().valueOf() + options.minutes * 60 * 1000);
285
+ } else if (options.hours) {
286
+ expirationTime = new Date(new Date().valueOf() + options.hours * 60 * 60 * 1000);
287
+ } else if (options.days) {
288
+ expirationTime = new Date(new Date().valueOf() + options.days * 24 * 60 * 60 * 1000);
289
+ } else if (options.expiryTimeSeconds !== undefined) {
290
+ // Legacy support for existing parameter
291
+ expirationTime = new Date(new Date().valueOf() + options.expiryTimeSeconds * 1000);
292
+ } else {
293
+ // Default to SAS_TOKEN_LIFE_DAYS environment variable
294
+ const defaultExpirySeconds = parseInt(SAS_TOKEN_LIFE_DAYS) * 24 * 60 * 60;
295
+ expirationTime = new Date(new Date().valueOf() + defaultExpirySeconds * 1000);
296
+ }
297
+
235
298
  const sasOptions = {
236
299
  containerName: containerClient.containerName,
237
300
  blobName: blobName,
238
- permissions: "r", // Read permission
301
+ permissions: options.permissions || "r", // Read permission
239
302
  startsOn: new Date(),
240
- expiresOn: new Date(new Date().valueOf() + expiryTimeSeconds * 1000),
303
+ expiresOn: expirationTime,
241
304
  };
242
305
 
243
306
  const sasToken = generateBlobSASQueryParameters(
@@ -248,9 +311,9 @@ const generateSASToken = (
248
311
  };
249
312
 
250
313
  //deletes blob that has the requestId
251
- async function deleteBlob(requestId) {
314
+ async function deleteBlob(requestId, containerName = null) {
252
315
  if (!requestId) throw new Error("Missing requestId parameter");
253
- const { containerClient } = await getBlobClient();
316
+ const { containerClient } = await getBlobClient(containerName);
254
317
  // List all blobs in the container
255
318
  const blobs = containerClient.listBlobsFlat();
256
319
 
@@ -280,12 +343,15 @@ function uploadBlob(
280
343
  saveToLocal = false,
281
344
  filePath = null,
282
345
  hash = null,
346
+ containerParam = null,
283
347
  ) {
284
348
  return new Promise((resolve, reject) => {
285
349
  (async () => {
286
350
  try {
287
351
  let requestId = uuidv4();
352
+ let containerName = containerParam;
288
353
  const body = {};
354
+ const fields = {}; // Buffer for all fields
289
355
 
290
356
  // If filePath is given, we are dealing with local file and not form-data
291
357
  if (filePath) {
@@ -308,29 +374,42 @@ function uploadBlob(
308
374
  uploadName, // Use the LLM-friendly filename
309
375
  resolve,
310
376
  hash,
377
+ containerName,
311
378
  );
312
379
  resolve(result);
313
380
  } catch (error) {
314
- const err = new Error("Error processing file upload.");
381
+ console.error("Error in uploadFile (local file path):", error);
382
+ const err = new Error(`Error processing file upload: ${error.message}`);
315
383
  err.status = 500;
316
384
  throw err;
317
385
  }
318
386
  } else {
319
- // Otherwise, continue working with form-data
320
387
  const busboy = Busboy({ headers: req.headers });
321
388
  let hasFile = false;
322
389
  let errorOccurred = false;
323
390
 
391
+
324
392
  busboy.on("field", (fieldname, value) => {
325
393
  if (fieldname === "requestId") {
326
394
  requestId = value;
327
395
  } else if (fieldname === "hash") {
328
396
  hash = value;
397
+ } else if (fieldname === "container") {
398
+ if (value && !isValidContainerName(value)) {
399
+ errorOccurred = true;
400
+ const err = new Error(`Invalid container name '${value}'. Allowed containers: ${AZURE_STORAGE_CONTAINER_NAMES.join(', ')}`);
401
+ err.status = 400;
402
+ reject(err);
403
+ return;
404
+ }
405
+ containerName = value;
329
406
  }
407
+ fields[fieldname] = value; // Store all fields
330
408
  });
331
409
 
332
410
  busboy.on("file", async (fieldname, file, info) => {
333
411
  if (errorOccurred) return;
412
+
334
413
  hasFile = true;
335
414
 
336
415
  // Validate file
@@ -342,6 +421,28 @@ function uploadBlob(
342
421
  return;
343
422
  }
344
423
 
424
+ // Simple approach: small delay to allow container field to be processed
425
+ console.log("File received, giving fields time to process...");
426
+ await new Promise(resolve => setTimeout(resolve, 20));
427
+ console.log("Processing file with containerName:", containerName);
428
+
429
+ if (errorOccurred) return; // Check again after waiting
430
+
431
+ await processFile(fieldname, file, info);
432
+ });
433
+
434
+ const processFile = async (fieldname, file, info) => {
435
+ if (errorOccurred) return;
436
+
437
+ // Validate file
438
+ if (!info.filename || info.filename.trim() === "") {
439
+ errorOccurred = true;
440
+ const err = new Error("Invalid file: missing filename");
441
+ err.status = 400;
442
+ reject(err);
443
+ return;
444
+ }
445
+
345
446
  // Prepare for streaming to cloud destinations
346
447
  const filename = info.filename;
347
448
  const fileExtension = path.extname(filename);
@@ -426,6 +527,7 @@ function uploadBlob(
426
527
  context,
427
528
  uploadName,
428
529
  azureStream,
530
+ containerName,
429
531
  ).catch(async (err) => {
430
532
  cloudUploadError = err;
431
533
  // Fallback: try from disk if available
@@ -435,7 +537,7 @@ function uploadBlob(
435
537
  highWaterMark: 1024 * 1024,
436
538
  autoClose: true,
437
539
  });
438
- return saveToAzureStorage(context, uploadName, diskStream);
540
+ return saveToAzureStorage(context, uploadName, diskStream, containerName);
439
541
  }
440
542
  throw err;
441
543
  });
@@ -557,6 +659,8 @@ function uploadBlob(
557
659
  await conversionService._saveConvertedFile(
558
660
  conversion.convertedPath,
559
661
  requestId,
662
+ null,
663
+ containerName,
560
664
  );
561
665
 
562
666
  // Optionally save to GCS
@@ -592,6 +696,8 @@ function uploadBlob(
592
696
  context.res = { status: 200, body: result };
593
697
  resolve(result);
594
698
  } catch (err) {
699
+ console.error("Error in main busboy processing:", err);
700
+ console.error("Stack trace:", err.stack);
595
701
  errorOccurred = true;
596
702
  reject(err);
597
703
  } finally {
@@ -600,7 +706,7 @@ function uploadBlob(
600
706
  fs.rmSync(tempDir, { recursive: true, force: true });
601
707
  }
602
708
  }
603
- });
709
+ };
604
710
 
605
711
  busboy.on("error", (error) => {
606
712
  if (errorOccurred) return;
@@ -611,7 +717,6 @@ function uploadBlob(
611
717
  });
612
718
 
613
719
  busboy.on("finish", () => {
614
- if (errorOccurred) return;
615
720
  if (!hasFile) {
616
721
  errorOccurred = true;
617
722
  const err = new Error("No file provided in request");
@@ -648,10 +753,10 @@ function uploadBlob(
648
753
  }
649
754
  }
650
755
  } catch (error) {
651
- // Only log unexpected errors
652
- if (error.message !== "No file provided in request") {
653
- context.log("Error processing file upload:", error);
654
- }
756
+ // Always log errors with full details for debugging
757
+ console.error("Top-level error processing file upload:", error);
758
+ console.error("Error stack trace:", error.stack);
759
+ context.log("Error processing file upload:", error);
655
760
  const err = new Error(error.message || "Error processing file upload.");
656
761
  err.status = error.status || 500;
657
762
  reject(err);
@@ -674,8 +779,8 @@ async function saveToLocalStorage(context, requestId, encodedFilename, file) {
674
779
  }
675
780
 
676
781
  // Helper function to handle Azure blob storage
677
- async function saveToAzureStorage(context, encodedFilename, file) {
678
- const { containerClient } = await getBlobClient();
782
+ async function saveToAzureStorage(context, encodedFilename, file, containerName = null) {
783
+ const { containerClient } = await getBlobClient(containerName);
679
784
  const contentType = mime.lookup(encodedFilename);
680
785
 
681
786
  // Create a safe blob name that is URI-encoded once (no double encoding)
@@ -689,6 +794,7 @@ async function saveToAzureStorage(context, encodedFilename, file) {
689
794
  };
690
795
 
691
796
  const blockBlobClient = containerClient.getBlockBlobClient(blobName);
797
+ console.log("Uploading to container:", containerName || "default");
692
798
  context.log(`Uploading to Azure... ${blobName}`);
693
799
  await blockBlobClient.uploadStream(file, undefined, undefined, options);
694
800
  const sasToken = generateSASToken(containerClient, blobName);
@@ -731,6 +837,7 @@ async function uploadFile(
731
837
  filename,
732
838
  resolve,
733
839
  hash = null,
840
+ containerName = null,
734
841
  ) {
735
842
  try {
736
843
  if (!file) {
@@ -782,16 +889,17 @@ async function uploadFile(
782
889
  context.log("Starting primary storage upload...");
783
890
  const primaryPromise = saveToLocal
784
891
  ? saveToLocalStorage(
785
- context,
786
- requestId,
787
- uploadName,
788
- createOptimizedReadStream(uploadPath),
789
- )
892
+ context,
893
+ requestId,
894
+ uploadName,
895
+ createOptimizedReadStream(uploadPath),
896
+ )
790
897
  : saveToAzureStorage(
791
- context,
792
- uploadName,
793
- createOptimizedReadStream(uploadPath),
794
- );
898
+ context,
899
+ uploadName,
900
+ createOptimizedReadStream(uploadPath),
901
+ containerName,
902
+ );
795
903
  storagePromises.push(
796
904
  primaryPromise.then((url) => {
797
905
  context.log("Primary storage upload completed");
@@ -854,6 +962,8 @@ async function uploadFile(
854
962
  await conversionService._saveConvertedFile(
855
963
  conversion.convertedPath,
856
964
  requestId,
965
+ null,
966
+ containerName,
857
967
  );
858
968
  context.log("Converted file saved to primary storage");
859
969
 
@@ -900,7 +1010,7 @@ async function uploadFile(
900
1010
  context.log("Error in upload process:", error);
901
1011
  if (body.url) {
902
1012
  try {
903
- await cleanup(context, [body.url]);
1013
+ await cleanup(context, [body.url], containerName);
904
1014
  } catch (cleanupError) {
905
1015
  context.log("Error during cleanup after failure:", cleanupError);
906
1016
  }
@@ -920,8 +1030,8 @@ async function streamToBuffer(stream) {
920
1030
  }
921
1031
 
922
1032
  // Function to delete files that haven't been used in more than a month
923
- async function cleanup(context, urls = null) {
924
- const { containerClient } = await getBlobClient();
1033
+ async function cleanup(context, urls = null, containerName = null) {
1034
+ const { containerClient } = await getBlobClient(containerName);
925
1035
  const cleanedURLs = [];
926
1036
 
927
1037
  if (!urls) {
@@ -1095,11 +1205,11 @@ async function deleteGCS(blobName) {
1095
1205
  errors: error.errors,
1096
1206
  response: error.response
1097
1207
  ? {
1098
- status: error.response.status,
1099
- statusText: error.response.statusText,
1100
- data: error.response.data,
1101
- headers: error.response.headers,
1102
- }
1208
+ status: error.response.status,
1209
+ statusText: error.response.statusText,
1210
+ data: error.response.data,
1211
+ headers: error.response.headers,
1212
+ }
1103
1213
  : null,
1104
1214
  });
1105
1215
  // Don't throw the error - we want to continue with cleanup even if GCS deletion fails
@@ -129,3 +129,6 @@ export const CONVERTED_EXTENSIONS = [
129
129
  ".ppt",
130
130
  ".pptx",
131
131
  ];
132
+
133
+ // Azure Storage constants
134
+ export const AZURITE_ACCOUNT_NAME = "devstoreaccount1";
@@ -37,14 +37,16 @@ async function cleanupTempDirectories() {
37
37
  }
38
38
  }
39
39
 
40
- // Setup periodic cleanup
41
- setInterval(async () => {
42
- try {
43
- await cleanupTempDirectories();
44
- } catch (err) {
45
- console.error("Error during periodic cleanup:", err);
46
- }
47
- }, CLEANUP_INTERVAL_MS);
40
+ // Setup periodic cleanup (only in production, not during tests)
41
+ if (process.env.NODE_ENV !== 'test') {
42
+ setInterval(async () => {
43
+ try {
44
+ await cleanupTempDirectories();
45
+ } catch (err) {
46
+ console.error("Error during periodic cleanup:", err);
47
+ }
48
+ }, CLEANUP_INTERVAL_MS);
49
+ }
48
50
 
49
51
  // Process a single chunk with streaming and progress tracking
50
52
  async function processChunk(inputPath, outputFileName, start, duration) {