@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,36 @@
1
+ [tool.poetry]
2
+ name = "cortex-autogen2"
3
+ version = "0.1.0"
4
+ description = "Multi-agent coding assistant using AutoGen"
5
+ authors = ["Your Name <your.email@example.com>"]
6
+ readme = "README.md"
7
+ packages = [{include = "cortex_autogen2", from = "src"}]
8
+
9
+ [tool.poetry.dependencies]
10
+ python = ">=3.11,<3.13"
11
+ autogen-agentchat = {extras = ["openai"], version = "^0.6.4"}
12
+ python-dotenv = "^1.0.1"
13
+ requests = "^2.32.3"
14
+ redis = "^5.0.4"
15
+ azure-storage-queue = "^12.10.1"
16
+ azure-storage-blob = "^12.19.0"
17
+ sqlalchemy = "^2.0.30"
18
+ Pillow = "^11.0.0"
19
+ pymysql = "^1.1.1"
20
+ playwright = "^1.54.0"
21
+ markitdown = "^0.1.2"
22
+ docker = "^7.1.0"
23
+ autogen-ext = {extras = ["docker"], version = "^0.6.4"}
24
+ openai = "^1.97.1"
25
+ tiktoken = "^0.9.0"
26
+ aiofiles = "^24.1.0"
27
+ pandas = "^2.3.1"
28
+ matplotlib = "^3.10.3"
29
+ aiohttp = "^3.12.14"
30
+
31
+ [tool.poetry.group.dev.dependencies]
32
+ ipykernel = "^6.29.5"
33
+
34
+ [build-system]
35
+ requires = ["poetry-core"]
36
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,20 @@
1
+ azure-functions>=1.20.0
2
+ autogen-agentchat[openai]>=0.6.4
3
+ python-dotenv>=1.0.1
4
+ requests>=2.32.3
5
+ redis>=5.0.4
6
+ azure-storage-queue>=12.10.1
7
+ azure-storage-blob>=12.19.0
8
+ sqlalchemy>=2.0.30
9
+ Pillow>=11.0.0
10
+ pymysql>=1.1.1
11
+ playwright>=1.54.0
12
+ markitdown>=0.1.2
13
+ docker>=7.1.0
14
+ autogen-ext[docker]>=0.6.4
15
+ openai>=1.97.1
16
+ tiktoken>=0.9.0
17
+ aiofiles>=24.1.0
18
+ aiohttp>=3.12.14
19
+ pandas
20
+ matplotlib
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 🚀 Cortex AutoGen Task Sender
4
+
5
+ Send a task to the Azure Storage Queue for processing by the Cortex AutoGen worker.
6
+
7
+ CRITICAL: Clean worker state prevents conflicts
8
+ ----------------------------------------------
9
+ 1) Kill existing workers:
10
+ pkill -f "python -m src.cortex_autogen2.main" || true
11
+ pkill -f "python main.py" || true
12
+
13
+ 2) Start fresh worker (non-continuous) in background:
14
+ CONTINUOUS_MODE=false python -m src.cortex_autogen2.main &
15
+ # or: CONTINUOUS_MODE=false python main.py &
16
+
17
+ 3) Send a task:
18
+ python send_task.py "Create a simple PDF about cats and upload it"
19
+
20
+ Why this order? Multiple workers can cause duplicate processing, queue conflicts, and noisy progress updates.
21
+
22
+ Usage
23
+ -----
24
+ python send_task.py "<your task text>" [--queue <name>] [--connection <conn_str>]
25
+
26
+ Environment
27
+ -----------
28
+ - AZURE_STORAGE_CONNECTION_STRING (required if --connection not provided)
29
+ - AZURE_QUEUE_NAME (default queue if --queue not provided)
30
+ - .env is loaded automatically
31
+
32
+ Message format
33
+ --------------
34
+ - Base64-encoded JSON payload with a `content` field:
35
+ {"request_id": "<uuid>", "message_id": "<uuid>", "content": "<task text>"}
36
+
37
+ Notes
38
+ -----
39
+ - Tasks persist in the queue until a worker processes them.
40
+ - Progress is published to Redis (see README for details).
41
+ """
42
+
43
+ import json
44
+ import uuid
45
+ import argparse
46
+ import base64
47
+ from azure.storage.queue import QueueClient
48
+ from dotenv import load_dotenv
49
+ import os
50
+
51
+ def main():
52
+ """Sends a simple task to the Azure Queue."""
53
+ load_dotenv()
54
+
55
+ parser = argparse.ArgumentParser(description="Send a task to the AutoGen agent processor.")
56
+ parser.add_argument(
57
+ "task_prompt",
58
+ type=str,
59
+ nargs='?',
60
+ default="list the files in the current directory",
61
+ help="The prompt for the task to be executed."
62
+ )
63
+ parser.add_argument(
64
+ "--queue",
65
+ dest="queue_name",
66
+ type=str,
67
+ default=os.getenv("AZURE_QUEUE_NAME", "autogen-test-message-queue"),
68
+ help="Azure Storage Queue name (overrides AZURE_QUEUE_NAME)."
69
+ )
70
+ parser.add_argument(
71
+ "--connection",
72
+ dest="connection_string",
73
+ type=str,
74
+ default=os.getenv("AZURE_STORAGE_CONNECTION_STRING"),
75
+ help="Azure Storage connection string (overrides AZURE_STORAGE_CONNECTION_STRING)."
76
+ )
77
+ args = parser.parse_args()
78
+
79
+ connection_string = args.connection_string
80
+ queue_name = args.queue_name
81
+ print(f"Using queue: {queue_name}")
82
+
83
+ if not connection_string:
84
+ print("Error: AZURE_STORAGE_CONNECTION_STRING is not set and --connection was not provided.")
85
+ return
86
+
87
+ task = {
88
+ "request_id": str(uuid.uuid4()),
89
+ "message_id": str(uuid.uuid4()),
90
+ "content": args.task_prompt
91
+ }
92
+
93
+ message = json.dumps(task)
94
+
95
+ # Encode message as Base64 to match Azure Functions MessageEncoding setting
96
+ encoded_message = base64.b64encode(message.encode('utf-8')).decode('utf-8')
97
+
98
+ # Use synchronous client for instant execution
99
+ queue_client = QueueClient.from_connection_string(connection_string, queue_name)
100
+ queue_client.send_message(encoded_message)
101
+ print(f"Task sent to queue '{queue_name}': {task}")
102
+ print(f"Message encoded as Base64: {encoded_message[:50]}...")
103
+
104
+ if __name__ == "__main__":
105
+ main()
@@ -0,0 +1,85 @@
1
+ import asyncio
2
+ from azure.storage.queue.aio import QueueServiceClient, QueueClient
3
+ from azure.core.exceptions import ResourceExistsError, AzureError
4
+ import os
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ class AzureQueueService:
10
+ """
11
+ A service for interacting with Azure Queue Storage.
12
+ """
13
+
14
+ def __init__(self, connection_string: str, queue_name: str):
15
+ self.connection_string = connection_string
16
+ self.queue_name = queue_name
17
+ self.queue_client = QueueClient.from_connection_string(
18
+ conn_str=self.connection_string, queue_name=self.queue_name
19
+ )
20
+
21
+ async def initialize(self):
22
+ """
23
+ Initializes the queue, creating it if it doesn't exist.
24
+ """
25
+ try:
26
+ await self.queue_client.create_queue()
27
+ except ResourceExistsError:
28
+ pass
29
+ except Exception as e:
30
+ logger.error(f"💥 Failed to create or connect to queue '{self.queue_name}': {e}")
31
+ raise
32
+
33
+ async def get_task(self) -> dict | None:
34
+ """
35
+ Receives a single message from the queue.
36
+ """
37
+ try:
38
+ messages = self.queue_client.receive_messages(
39
+ messages_per_page=1,
40
+ visibility_timeout=1800,
41
+ timeout=30
42
+ )
43
+
44
+ async for message in messages:
45
+ logger.info(f"📨 Azure Queue: Received message with ID: {message.id}")
46
+ return {
47
+ "id": message.id,
48
+ "content": message.content,
49
+ "pop_receipt": message.pop_receipt,
50
+ }
51
+ return None
52
+
53
+ except AzureError as e:
54
+ logger.error(f"❌ Azure Queue: An Azure-specific error occurred: {e}")
55
+ return None
56
+ except Exception as e:
57
+ logger.error(f"❌ Azure Queue: Unexpected error receiving message: {e}", exc_info=True)
58
+ return None
59
+
60
+ async def delete_task(self, message_id: str, pop_receipt: str):
61
+ """
62
+ Deletes a message from the queue after it has been processed.
63
+ """
64
+ try:
65
+ await self.queue_client.delete_message(message_id, pop_receipt)
66
+ except Exception as e:
67
+ logger.error(f"💥 Failed to delete message {message_id}: {e}")
68
+ raise
69
+
70
+ async def close(self):
71
+ """
72
+ Closes the QueueClient.
73
+ """
74
+ await self.queue_client.close()
75
+
76
+ async def get_queue_service() -> AzureQueueService:
77
+ """
78
+ Factory function to create and initialize an AzureQueueService instance.
79
+ """
80
+ connection_string = os.getenv("AZURE_STORAGE_CONNECTION_STRING")
81
+ queue_name = os.getenv("AZURE_QUEUE_NAME")
82
+
83
+ queue_service = AzureQueueService(connection_string, queue_name)
84
+ await queue_service.initialize()
85
+ return queue_service
@@ -0,0 +1,153 @@
1
+ import redis
2
+ import json
3
+ import logging
4
+ from typing import Dict, Any, Optional
5
+
6
+ import os
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # Global Redis client - persistent connection like the working version
11
+ redis_client = None
12
+
13
+ def connect_redis() -> bool:
14
+ """Check and ensure Redis connection is active - matches working version pattern"""
15
+ global redis_client
16
+
17
+ redis_conn_string = os.getenv("REDIS_CONNECTION_STRING")
18
+
19
+ # Initialize client if not exists
20
+ if redis_client is None:
21
+ try:
22
+ redis_client = redis.from_url(redis_conn_string)
23
+ except Exception as e:
24
+ logger.warning(f"Failed to create Redis client: {e}")
25
+ return False
26
+
27
+ # Test connection
28
+ try:
29
+ redis_client.ping()
30
+ return True
31
+ except redis.ConnectionError as e:
32
+ logger.warning(f"Redis connection error: {e}")
33
+ try:
34
+ # Try to reconnect
35
+ redis_client = redis.from_url(redis_conn_string)
36
+ redis_client.ping()
37
+ return True
38
+ except Exception as reconnect_error:
39
+ logger.error(f"Error reconnecting to Redis: {reconnect_error}")
40
+ return False
41
+ except Exception as e:
42
+ logger.warning(f"Redis ping failed: {e}")
43
+ # Handle the case where client is closed
44
+ if "Client must be connected" in str(e) or "closed" in str(e).lower():
45
+ logger.info("Redis client was closed, attempting to create new connection...")
46
+ try:
47
+ redis_client = redis.from_url(redis_conn_string)
48
+ redis_client.ping()
49
+ return True
50
+ except Exception as reconnect_error:
51
+ logger.error(f"Error creating new Redis connection: {reconnect_error}")
52
+ return False
53
+ return False
54
+
55
+ def publish_request_progress(data: Dict[str, Any]) -> bool:
56
+ """Publish progress data to Redis channel - matches working version pattern"""
57
+ if connect_redis():
58
+ try:
59
+ message = json.dumps(data)
60
+ result = redis_client.publish(os.getenv("REDIS_CHANNEL"), message)
61
+ logger.info(f"Published progress update for request {data.get('requestId')}: progress={data.get('progress')}, subscribers={result}")
62
+ return True
63
+ except Exception as e:
64
+ logger.error(f"Error publishing message to Redis: {e}")
65
+ return False
66
+ else:
67
+ logger.error(f"Redis not connected, failed to publish progress update for request {data.get('requestId')}")
68
+ return False
69
+
70
+ class RedisPublisher:
71
+ """Wrapper class for compatibility with existing code"""
72
+
73
+ def __init__(self):
74
+ self.connected = False
75
+
76
+ async def connect(self):
77
+ """Initialize Redis connection"""
78
+ self.connected = connect_redis()
79
+ if self.connected:
80
+ logger.info("Connected to Redis successfully")
81
+ else:
82
+ logger.warning("Failed to connect to Redis")
83
+ logger.warning("Redis progress publishing will be disabled")
84
+
85
+ def publish_request_progress(self, data: Dict[str, Any]) -> bool:
86
+ """Publish progress data to Redis channel"""
87
+ return publish_request_progress(data)
88
+
89
+ async def publish_progress(self, request_id: str, progress: float, info: str = "", data: str = None) -> bool:
90
+ """Publish progress update for a specific request - async version"""
91
+ message_data = {
92
+ "requestId": request_id,
93
+ "progress": progress,
94
+ "info": info
95
+ }
96
+
97
+ # Add data field for final results
98
+ if data is not None:
99
+ message_data["data"] = data
100
+
101
+ return self.publish_request_progress(message_data)
102
+
103
+ def store_final_result(self, request_id: str, result_data: Dict[str, Any], expiry_seconds: int = 3600) -> bool:
104
+ """Store final result in Redis key for retrieval"""
105
+ if connect_redis():
106
+ try:
107
+ # Store in multiple keys for compatibility
108
+ keys_to_store = [
109
+ f"result:{request_id}",
110
+ f"final:{request_id}",
111
+ f"progress:{request_id}" # Also store in progress key for consistency
112
+ ]
113
+
114
+ message = json.dumps(result_data)
115
+
116
+ for key in keys_to_store:
117
+ redis_client.set(key, message, ex=expiry_seconds)
118
+
119
+ logger.info(f"Stored final result for request {request_id} in {len(keys_to_store)} Redis keys")
120
+ return True
121
+
122
+ except Exception as e:
123
+ logger.error(f"Error storing final result: {e}")
124
+ return False
125
+ else:
126
+ logger.debug(f"Redis not connected, skipping final result storage for request {request_id}")
127
+ return False
128
+
129
+ async def close(self):
130
+ """Close Redis connection gracefully"""
131
+ global redis_client
132
+ if redis_client:
133
+ try:
134
+ # Don't actually close the connection in non-continuous mode
135
+ # Just mark it as disconnected so it can be recreated if needed
136
+ logger.info("Redis connection marked for cleanup")
137
+ # Only close if we're in continuous mode or shutting down completely
138
+ # redis_client.close() # Comment out to prevent premature closure
139
+ # redis_client = None
140
+ except Exception as e:
141
+ logger.warning(f"Error during Redis connection cleanup: {e}")
142
+ self.connected = False
143
+
144
+ # Global instance
145
+ _redis_publisher: Optional[RedisPublisher] = None
146
+
147
+ async def get_redis_publisher() -> RedisPublisher:
148
+ """Get or create Redis publisher instance"""
149
+ global _redis_publisher
150
+ if _redis_publisher is None:
151
+ _redis_publisher = RedisPublisher()
152
+ await _redis_publisher.connect()
153
+ return _redis_publisher