@_vrsen/openswarm 0.1.0

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 (316) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/bin/openswarm.js +38 -0
  4. package/config.py +34 -0
  5. package/data_analyst_agent/.cursor/rules/data_analyst.mdc +43 -0
  6. package/data_analyst_agent/__init__.py +3 -0
  7. package/data_analyst_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  8. package/data_analyst_agent/__pycache__/data_analyst_agent.cpython-312.pyc +0 -0
  9. package/data_analyst_agent/data_analyst_agent.py +45 -0
  10. package/data_analyst_agent/instructions.md +173 -0
  11. package/data_analyst_agent/test_files/test_file.csv +21 -0
  12. package/data_analyst_agent/tools/__init__.py +6 -0
  13. package/deep_research/__init__.py +1 -0
  14. package/deep_research/__pycache__/__init__.cpython-312.pyc +0 -0
  15. package/deep_research/__pycache__/deep_research.cpython-312.pyc +0 -0
  16. package/deep_research/deep_research.py +27 -0
  17. package/deep_research/instructions.md +104 -0
  18. package/deep_research/tools/__init__.py +1 -0
  19. package/docs_agent/__init__.py +3 -0
  20. package/docs_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/docs_agent/__pycache__/docs_agent.cpython-312.pyc +0 -0
  22. package/docs_agent/docs_agent.py +61 -0
  23. package/docs_agent/instructions.md +418 -0
  24. package/docs_agent/tools/ConvertDocument.py +323 -0
  25. package/docs_agent/tools/CreateDocument.py +287 -0
  26. package/docs_agent/tools/ListDocuments.py +134 -0
  27. package/docs_agent/tools/ModifyDocument.py +247 -0
  28. package/docs_agent/tools/RestoreDocument.py +79 -0
  29. package/docs_agent/tools/ViewDocument.py +153 -0
  30. package/docs_agent/tools/__init__.py +1 -0
  31. package/docs_agent/tools/__pycache__/ConvertDocument.cpython-312.pyc +0 -0
  32. package/docs_agent/tools/__pycache__/CreateDocument.cpython-312.pyc +0 -0
  33. package/docs_agent/tools/__pycache__/ListDocuments.cpython-312.pyc +0 -0
  34. package/docs_agent/tools/__pycache__/ModifyDocument.cpython-312.pyc +0 -0
  35. package/docs_agent/tools/__pycache__/RestoreDocument.cpython-312.pyc +0 -0
  36. package/docs_agent/tools/__pycache__/ViewDocument.cpython-312.pyc +0 -0
  37. package/docs_agent/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  38. package/docs_agent/tools/utils/__init__.py +1 -0
  39. package/docs_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  40. package/docs_agent/tools/utils/__pycache__/doc_file_utils.cpython-312.pyc +0 -0
  41. package/docs_agent/tools/utils/__pycache__/html_docx_blocks.cpython-312.pyc +0 -0
  42. package/docs_agent/tools/utils/__pycache__/html_docx_constants.cpython-312.pyc +0 -0
  43. package/docs_agent/tools/utils/__pycache__/html_docx_core.cpython-312.pyc +0 -0
  44. package/docs_agent/tools/utils/__pycache__/html_docx_css.cpython-312.pyc +0 -0
  45. package/docs_agent/tools/utils/__pycache__/html_docx_images.cpython-312.pyc +0 -0
  46. package/docs_agent/tools/utils/__pycache__/html_docx_page.cpython-312.pyc +0 -0
  47. package/docs_agent/tools/utils/__pycache__/html_docx_paragraphs.cpython-312.pyc +0 -0
  48. package/docs_agent/tools/utils/__pycache__/html_docx_playwright.cpython-312.pyc +0 -0
  49. package/docs_agent/tools/utils/__pycache__/html_docx_selectors.cpython-312.pyc +0 -0
  50. package/docs_agent/tools/utils/__pycache__/html_docx_shared.cpython-312.pyc +0 -0
  51. package/docs_agent/tools/utils/__pycache__/html_validation.cpython-312.pyc +0 -0
  52. package/docs_agent/tools/utils/doc_file_utils.py +29 -0
  53. package/docs_agent/tools/utils/html_docx_blocks.py +262 -0
  54. package/docs_agent/tools/utils/html_docx_constants.py +78 -0
  55. package/docs_agent/tools/utils/html_docx_core.py +138 -0
  56. package/docs_agent/tools/utils/html_docx_css.py +262 -0
  57. package/docs_agent/tools/utils/html_docx_images.py +293 -0
  58. package/docs_agent/tools/utils/html_docx_page.py +185 -0
  59. package/docs_agent/tools/utils/html_docx_paragraphs.py +342 -0
  60. package/docs_agent/tools/utils/html_docx_playwright.py +184 -0
  61. package/docs_agent/tools/utils/html_docx_selectors.py +196 -0
  62. package/docs_agent/tools/utils/html_docx_shared.py +23 -0
  63. package/docs_agent/tools/utils/html_docx_tables.py +942 -0
  64. package/docs_agent/tools/utils/html_validation.py +102 -0
  65. package/helpers.py +59 -0
  66. package/image_generation_agent/__init__.py +1 -0
  67. package/image_generation_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  68. package/image_generation_agent/__pycache__/image_generation_agent.cpython-312.pyc +0 -0
  69. package/image_generation_agent/image_generation_agent.py +31 -0
  70. package/image_generation_agent/instructions.md +80 -0
  71. package/image_generation_agent/tools/CombineImages.py +211 -0
  72. package/image_generation_agent/tools/EditImages.py +200 -0
  73. package/image_generation_agent/tools/GenerateImages.py +184 -0
  74. package/image_generation_agent/tools/RemoveBackground.py +108 -0
  75. package/image_generation_agent/tools/__init__.py +2 -0
  76. package/image_generation_agent/tools/__pycache__/CombineImages.cpython-312.pyc +0 -0
  77. package/image_generation_agent/tools/__pycache__/EditImages.cpython-312.pyc +0 -0
  78. package/image_generation_agent/tools/__pycache__/GenerateImages.cpython-312.pyc +0 -0
  79. package/image_generation_agent/tools/__pycache__/RemoveBackground.cpython-312.pyc +0 -0
  80. package/image_generation_agent/tools/utils/__init__.py +2 -0
  81. package/image_generation_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  82. package/image_generation_agent/tools/utils/__pycache__/image_io.cpython-312.pyc +0 -0
  83. package/image_generation_agent/tools/utils/image_io.py +308 -0
  84. package/onboard.py +325 -0
  85. package/orchestrator/__init__.py +3 -0
  86. package/orchestrator/__pycache__/__init__.cpython-312.pyc +0 -0
  87. package/orchestrator/__pycache__/orchestrator.cpython-312.pyc +0 -0
  88. package/orchestrator/instructions.md +90 -0
  89. package/orchestrator/orchestrator.py +33 -0
  90. package/package.json +49 -0
  91. package/patches/__init__.py +1 -0
  92. package/patches/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/patches/__pycache__/patch_agency_swarm_dual_comms.cpython-312.pyc +0 -0
  94. package/patches/__pycache__/patch_file_attachment_refs.cpython-312.pyc +0 -0
  95. package/patches/__pycache__/patch_ipython_interpreter_composio.cpython-312.pyc +0 -0
  96. package/patches/dom-to-pptx+1.1.5.patch +133440 -0
  97. package/patches/patch_agency_swarm_dual_comms.py +199 -0
  98. package/patches/patch_file_attachment_refs.py +74 -0
  99. package/patches/patch_ipython_interpreter_composio.py +54 -0
  100. package/pyproject.toml +67 -0
  101. package/run.py +343 -0
  102. package/server.py +26 -0
  103. package/shared_instructions.md +119 -0
  104. package/shared_tools/CopyFile.py +68 -0
  105. package/shared_tools/ExecuteTool.py +184 -0
  106. package/shared_tools/FindTools.py +101 -0
  107. package/shared_tools/ManageConnections.py +43 -0
  108. package/shared_tools/SearchTools.py +44 -0
  109. package/shared_tools/__init__.py +7 -0
  110. package/shared_tools/__pycache__/CopyFile.cpython-312.pyc +0 -0
  111. package/shared_tools/__pycache__/ExecuteTool.cpython-312.pyc +0 -0
  112. package/shared_tools/__pycache__/FindTools.cpython-312.pyc +0 -0
  113. package/shared_tools/__pycache__/ManageConnections.cpython-312.pyc +0 -0
  114. package/shared_tools/__pycache__/SearchTools.cpython-312.pyc +0 -0
  115. package/shared_tools/__pycache__/__init__.cpython-312.pyc +0 -0
  116. package/slides_agent/.cursor/rules/slides-agent-workflow.mdc +9 -0
  117. package/slides_agent/__init__.py +1 -0
  118. package/slides_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  119. package/slides_agent/__pycache__/slides_agent.cpython-312.pyc +0 -0
  120. package/slides_agent/instructions.md +298 -0
  121. package/slides_agent/pptx/SKILL.md +528 -0
  122. package/slides_agent/pptx/html2pptx.md +625 -0
  123. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  124. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  125. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  126. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  127. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  128. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  129. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  130. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  131. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  132. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  133. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  134. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  135. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  136. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  137. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  138. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  139. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  140. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  141. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  142. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  143. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  144. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  145. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  146. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  147. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  148. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  149. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  150. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  151. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  152. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  153. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  154. package/slides_agent/pptx/ooxml/schemas/mce/mc.xsd +75 -0
  155. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  156. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  157. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  158. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  159. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  160. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  161. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  162. package/slides_agent/pptx/ooxml/scripts/pack.py +169 -0
  163. package/slides_agent/pptx/ooxml/scripts/unpack.py +29 -0
  164. package/slides_agent/pptx/ooxml/scripts/validate.py +69 -0
  165. package/slides_agent/pptx/ooxml/scripts/validation/__init__.py +15 -0
  166. package/slides_agent/pptx/ooxml/scripts/validation/base.py +951 -0
  167. package/slides_agent/pptx/ooxml/scripts/validation/docx.py +274 -0
  168. package/slides_agent/pptx/ooxml/scripts/validation/pptx.py +315 -0
  169. package/slides_agent/pptx/ooxml/scripts/validation/redlining.py +279 -0
  170. package/slides_agent/pptx/ooxml.md +427 -0
  171. package/slides_agent/pptx/scripts/html2pptx.js +1092 -0
  172. package/slides_agent/pptx/scripts/inventory.py +1020 -0
  173. package/slides_agent/pptx/scripts/rearrange.py +231 -0
  174. package/slides_agent/pptx/scripts/replace.py +385 -0
  175. package/slides_agent/pptx/scripts/thumbnail.py +451 -0
  176. package/slides_agent/slides_agent.py +109 -0
  177. package/slides_agent/test_deck/_theme.css +92 -0
  178. package/slides_agent/test_deck/assets/placeholder.svg +11 -0
  179. package/slides_agent/test_deck/slide_01_title.html +10 -0
  180. package/slides_agent/test_deck/slide_02_image_split.html +23 -0
  181. package/slides_agent/test_deck/slide_03_kpi.html +21 -0
  182. package/slides_agent/tools/ApplyPptxTextReplacements.py +91 -0
  183. package/slides_agent/tools/BuildPptxFromHtmlSlides.py +221 -0
  184. package/slides_agent/tools/CheckSlide.py +218 -0
  185. package/slides_agent/tools/CheckSlideCanvasOverflow.py +221 -0
  186. package/slides_agent/tools/CreateImageMontage.py +261 -0
  187. package/slides_agent/tools/CreatePptxThumbnailGrid.py +168 -0
  188. package/slides_agent/tools/DeleteSlide.py +78 -0
  189. package/slides_agent/tools/DownloadImage.py +79 -0
  190. package/slides_agent/tools/EnsureRasterImage.py +157 -0
  191. package/slides_agent/tools/ExtractPptxTextInventory.py +104 -0
  192. package/slides_agent/tools/GenerateImage.py +189 -0
  193. package/slides_agent/tools/ImageSearch.py +127 -0
  194. package/slides_agent/tools/InsertNewSlides.py +393 -0
  195. package/slides_agent/tools/ManageTheme.py +112 -0
  196. package/slides_agent/tools/ModifySlide.py +563 -0
  197. package/slides_agent/tools/ReadSlide.py +26 -0
  198. package/slides_agent/tools/RearrangePptxSlidesFromTemplate.py +114 -0
  199. package/slides_agent/tools/RestoreSnapshot.py +89 -0
  200. package/slides_agent/tools/SlideScreenshot.py +66 -0
  201. package/slides_agent/tools/__init__.py +54 -0
  202. package/slides_agent/tools/__pycache__/ApplyPptxTextReplacements.cpython-312.pyc +0 -0
  203. package/slides_agent/tools/__pycache__/BuildPptxFromHtmlSlides.cpython-312.pyc +0 -0
  204. package/slides_agent/tools/__pycache__/CheckSlide.cpython-312.pyc +0 -0
  205. package/slides_agent/tools/__pycache__/CheckSlideCanvasOverflow.cpython-312.pyc +0 -0
  206. package/slides_agent/tools/__pycache__/CreateImageMontage.cpython-312.pyc +0 -0
  207. package/slides_agent/tools/__pycache__/CreatePptxThumbnailGrid.cpython-312.pyc +0 -0
  208. package/slides_agent/tools/__pycache__/DeleteSlide.cpython-312.pyc +0 -0
  209. package/slides_agent/tools/__pycache__/DownloadImage.cpython-312.pyc +0 -0
  210. package/slides_agent/tools/__pycache__/EnsureRasterImage.cpython-312.pyc +0 -0
  211. package/slides_agent/tools/__pycache__/ExtractPptxTextInventory.cpython-312.pyc +0 -0
  212. package/slides_agent/tools/__pycache__/GenerateImage.cpython-312.pyc +0 -0
  213. package/slides_agent/tools/__pycache__/ImageSearch.cpython-312.pyc +0 -0
  214. package/slides_agent/tools/__pycache__/InsertNewSlides.cpython-312.pyc +0 -0
  215. package/slides_agent/tools/__pycache__/ManageTheme.cpython-312.pyc +0 -0
  216. package/slides_agent/tools/__pycache__/ModifySlide.cpython-312.pyc +0 -0
  217. package/slides_agent/tools/__pycache__/ReadSlide.cpython-312.pyc +0 -0
  218. package/slides_agent/tools/__pycache__/RearrangePptxSlidesFromTemplate.cpython-312.pyc +0 -0
  219. package/slides_agent/tools/__pycache__/RestoreSnapshot.cpython-312.pyc +0 -0
  220. package/slides_agent/tools/__pycache__/SlideScreenshot.cpython-312.pyc +0 -0
  221. package/slides_agent/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  222. package/slides_agent/tools/__pycache__/slide_file_utils.cpython-312.pyc +0 -0
  223. package/slides_agent/tools/__pycache__/slide_html_utils.cpython-312.pyc +0 -0
  224. package/slides_agent/tools/__pycache__/template_registry.cpython-312.pyc +0 -0
  225. package/slides_agent/tools/deck_utils.py +31 -0
  226. package/slides_agent/tools/html2pptx_runner.js +1183 -0
  227. package/slides_agent/tools/html_writer_instructions.md +149 -0
  228. package/slides_agent/tools/slide_file_utils.py +108 -0
  229. package/slides_agent/tools/slide_html_utils.py +354 -0
  230. package/slides_agent/tools/template_registry.py +55 -0
  231. package/swarm.py +82 -0
  232. package/video_generation_agent/__init__.py +1 -0
  233. package/video_generation_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  234. package/video_generation_agent/__pycache__/video_generation_agent.cpython-312.pyc +0 -0
  235. package/video_generation_agent/instructions.md +178 -0
  236. package/video_generation_agent/tools/AddSubtitles.py +425 -0
  237. package/video_generation_agent/tools/CombineImages.py +166 -0
  238. package/video_generation_agent/tools/CombineVideos.py +113 -0
  239. package/video_generation_agent/tools/EditAudio.py +297 -0
  240. package/video_generation_agent/tools/EditImage.py +144 -0
  241. package/video_generation_agent/tools/EditVideoContent.py +369 -0
  242. package/video_generation_agent/tools/GenerateImage.py +133 -0
  243. package/video_generation_agent/tools/GenerateVideo.py +556 -0
  244. package/video_generation_agent/tools/TrimVideo.py +233 -0
  245. package/video_generation_agent/tools/__init__.py +1 -0
  246. package/video_generation_agent/tools/__pycache__/AddSubtitles.cpython-312.pyc +0 -0
  247. package/video_generation_agent/tools/__pycache__/CombineImages.cpython-312.pyc +0 -0
  248. package/video_generation_agent/tools/__pycache__/CombineVideos.cpython-312.pyc +0 -0
  249. package/video_generation_agent/tools/__pycache__/EditAudio.cpython-312.pyc +0 -0
  250. package/video_generation_agent/tools/__pycache__/EditImage.cpython-312.pyc +0 -0
  251. package/video_generation_agent/tools/__pycache__/EditVideoContent.cpython-312.pyc +0 -0
  252. package/video_generation_agent/tools/__pycache__/GenerateImage.cpython-312.pyc +0 -0
  253. package/video_generation_agent/tools/__pycache__/GenerateVideo.cpython-312.pyc +0 -0
  254. package/video_generation_agent/tools/__pycache__/TrimVideo.cpython-312.pyc +0 -0
  255. package/video_generation_agent/tools/utils/__init__.py +1 -0
  256. package/video_generation_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  257. package/video_generation_agent/tools/utils/__pycache__/image_utils.cpython-312.pyc +0 -0
  258. package/video_generation_agent/tools/utils/__pycache__/video_utils.cpython-312.pyc +0 -0
  259. package/video_generation_agent/tools/utils/image_utils.py +174 -0
  260. package/video_generation_agent/tools/utils/video_utils.py +522 -0
  261. package/video_generation_agent/video_generation_agent.py +26 -0
  262. package/virtual_assistant/__init__.py +1 -0
  263. package/virtual_assistant/__pycache__/__init__.cpython-312.pyc +0 -0
  264. package/virtual_assistant/__pycache__/virtual_assistant.cpython-312.pyc +0 -0
  265. package/virtual_assistant/instructions.md +206 -0
  266. package/virtual_assistant/tools/AddLabelToEmail.py +154 -0
  267. package/virtual_assistant/tools/CheckEventsForDate.py +218 -0
  268. package/virtual_assistant/tools/CheckUnreadSlackMessages.py +216 -0
  269. package/virtual_assistant/tools/CreateCalendarEvent.py +261 -0
  270. package/virtual_assistant/tools/DeleteCalendarEvent.py +137 -0
  271. package/virtual_assistant/tools/DeleteDraft.py +95 -0
  272. package/virtual_assistant/tools/DraftEmail.py +239 -0
  273. package/virtual_assistant/tools/EditFile.py +113 -0
  274. package/virtual_assistant/tools/FindEmails.py +330 -0
  275. package/virtual_assistant/tools/GetCurrentTime.py +69 -0
  276. package/virtual_assistant/tools/GetSlackUserInfo.py +117 -0
  277. package/virtual_assistant/tools/ListDirectory.py +113 -0
  278. package/virtual_assistant/tools/ListSkills.py +94 -0
  279. package/virtual_assistant/tools/ManageLabels.py +295 -0
  280. package/virtual_assistant/tools/ProductSearch.py +254 -0
  281. package/virtual_assistant/tools/ReadEmail.py +251 -0
  282. package/virtual_assistant/tools/ReadFile.py +108 -0
  283. package/virtual_assistant/tools/ReadSlackMessages.py +191 -0
  284. package/virtual_assistant/tools/RemoveLabelFromEmail.py +137 -0
  285. package/virtual_assistant/tools/RescheduleCalendarEvent.py +227 -0
  286. package/virtual_assistant/tools/ScholarSearch.py +216 -0
  287. package/virtual_assistant/tools/SendDraft.py +101 -0
  288. package/virtual_assistant/tools/SendSlackMessage.py +148 -0
  289. package/virtual_assistant/tools/WriteFile.py +95 -0
  290. package/virtual_assistant/tools/__init__.py +1 -0
  291. package/virtual_assistant/tools/__pycache__/AddLabelToEmail.cpython-312.pyc +0 -0
  292. package/virtual_assistant/tools/__pycache__/CheckEventsForDate.cpython-312.pyc +0 -0
  293. package/virtual_assistant/tools/__pycache__/CheckUnreadSlackMessages.cpython-312.pyc +0 -0
  294. package/virtual_assistant/tools/__pycache__/CreateCalendarEvent.cpython-312.pyc +0 -0
  295. package/virtual_assistant/tools/__pycache__/DeleteCalendarEvent.cpython-312.pyc +0 -0
  296. package/virtual_assistant/tools/__pycache__/DeleteDraft.cpython-312.pyc +0 -0
  297. package/virtual_assistant/tools/__pycache__/DraftEmail.cpython-312.pyc +0 -0
  298. package/virtual_assistant/tools/__pycache__/EditFile.cpython-312.pyc +0 -0
  299. package/virtual_assistant/tools/__pycache__/FindEmails.cpython-312.pyc +0 -0
  300. package/virtual_assistant/tools/__pycache__/GetCurrentTime.cpython-312.pyc +0 -0
  301. package/virtual_assistant/tools/__pycache__/GetSlackUserInfo.cpython-312.pyc +0 -0
  302. package/virtual_assistant/tools/__pycache__/ListDirectory.cpython-312.pyc +0 -0
  303. package/virtual_assistant/tools/__pycache__/ListSkills.cpython-312.pyc +0 -0
  304. package/virtual_assistant/tools/__pycache__/ManageLabels.cpython-312.pyc +0 -0
  305. package/virtual_assistant/tools/__pycache__/ProductSearch.cpython-312.pyc +0 -0
  306. package/virtual_assistant/tools/__pycache__/ReadEmail.cpython-312.pyc +0 -0
  307. package/virtual_assistant/tools/__pycache__/ReadFile.cpython-312.pyc +0 -0
  308. package/virtual_assistant/tools/__pycache__/ReadSlackMessages.cpython-312.pyc +0 -0
  309. package/virtual_assistant/tools/__pycache__/RemoveLabelFromEmail.cpython-312.pyc +0 -0
  310. package/virtual_assistant/tools/__pycache__/RescheduleCalendarEvent.cpython-312.pyc +0 -0
  311. package/virtual_assistant/tools/__pycache__/ScholarSearch.cpython-312.pyc +0 -0
  312. package/virtual_assistant/tools/__pycache__/SendDraft.cpython-312.pyc +0 -0
  313. package/virtual_assistant/tools/__pycache__/SendSlackMessage.cpython-312.pyc +0 -0
  314. package/virtual_assistant/tools/__pycache__/WriteFile.cpython-312.pyc +0 -0
  315. package/virtual_assistant/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  316. package/virtual_assistant/virtual_assistant.py +52 -0
@@ -0,0 +1,251 @@
1
+ from typing import Literal
2
+ from agency_swarm.tools import BaseTool
3
+ from pydantic import Field
4
+ import json
5
+ import re
6
+
7
+ from helpers import execute_composio_tool
8
+
9
+
10
+ def strip_html(html_content: str) -> str:
11
+ """Converts HTML to plain text by removing tags and decoding entities."""
12
+ if not html_content:
13
+ return ""
14
+
15
+ # Remove style and script tags with their content
16
+ text = re.sub(r'<style[^>]*>.*?</style>', '', html_content, flags=re.DOTALL | re.IGNORECASE)
17
+ text = re.sub(r'<script[^>]*>.*?</script>', '', text, flags=re.DOTALL | re.IGNORECASE)
18
+
19
+ # Replace common block elements with newlines
20
+ text = re.sub(r'<br\s*/?>', '\n', text, flags=re.IGNORECASE)
21
+ text = re.sub(r'</(p|div|tr|li|h[1-6])>', '\n', text, flags=re.IGNORECASE)
22
+ text = re.sub(r'</td>', '\t', text, flags=re.IGNORECASE)
23
+
24
+ # Remove all remaining HTML tags
25
+ text = re.sub(r'<[^>]+>', '', text)
26
+
27
+ # Decode common HTML entities
28
+ text = text.replace('&nbsp;', ' ')
29
+ text = text.replace('&amp;', '&')
30
+ text = text.replace('&lt;', '<')
31
+ text = text.replace('&gt;', '>')
32
+ text = text.replace('&quot;', '"')
33
+ text = text.replace('&#39;', "'")
34
+ text = text.replace('&apos;', "'")
35
+
36
+ # Clean up whitespace
37
+ text = re.sub(r'\n\s*\n', '\n\n', text) # Collapse multiple newlines
38
+ text = re.sub(r'[ \t]+', ' ', text) # Collapse multiple spaces
39
+ text = '\n'.join(line.strip() for line in text.split('\n')) # Strip each line
40
+ text = text.strip()
41
+
42
+ return text
43
+
44
+
45
+ class ReadEmail(BaseTool):
46
+ """
47
+ Reads the full content of a specific email by its message ID.
48
+
49
+ Use this after CheckUnreadEmails to fetch the complete email content
50
+ when you need to read the full message body.
51
+ """
52
+
53
+ provider: Literal["gmail", "outlook"] = Field(
54
+ ...,
55
+ description="Email provider: 'gmail' or 'outlook'"
56
+ )
57
+
58
+ message_id: str = Field(
59
+ ...,
60
+ description="The message ID to fetch (obtained from CheckUnreadEmails)"
61
+ )
62
+
63
+ body_format: Literal["text", "html"] = Field(
64
+ default="text",
65
+ description="Format for the email body: 'text' (plain text, default) or 'html' (raw HTML). Keep the default to save tokens."
66
+ )
67
+
68
+ def run(self):
69
+ try:
70
+ if self.provider == "gmail":
71
+ return self._read_gmail_message(execute_composio_tool)
72
+ else:
73
+ return self._read_outlook_message(execute_composio_tool)
74
+
75
+ except Exception as e:
76
+ return f"Error reading email: {str(e)}"
77
+
78
+ def _read_gmail_message(self, execute_tool) -> str:
79
+ """Reads a Gmail message by ID."""
80
+ result = execute_tool(
81
+ tool_name="GMAIL_FETCH_MESSAGE_BY_MESSAGE_ID",
82
+ arguments={
83
+ "user_id": "me",
84
+ "message_id": self.message_id,
85
+ "format": "full"
86
+ },
87
+ )
88
+
89
+ if isinstance(result, dict) and result.get("error"):
90
+ return f"Error reading Gmail message: {result.get('error')}"
91
+
92
+ data = result.get("data", {})
93
+
94
+ if not data:
95
+ return f"Message not found: {self.message_id}"
96
+
97
+ if self.body_format == "text":
98
+ # Use preview body (clean plain text) if available, fallback to stripping HTML
99
+ preview = data.get("preview", {})
100
+ body_content = preview.get("body", "")
101
+ if not body_content:
102
+ body_content = strip_html(data.get("messageText", ""))
103
+ else:
104
+ body_content = data.get("messageText", "")
105
+
106
+ return json.dumps({
107
+ "provider": "gmail",
108
+ "message_id": data.get("messageId"),
109
+ "thread_id": data.get("threadId"),
110
+ "subject": data.get("subject", "(No subject)"),
111
+ "from": data.get("sender", "Unknown"),
112
+ "to": data.get("to", ""),
113
+ "received_at": data.get("messageTimestamp"),
114
+ "labels": data.get("labelIds", []),
115
+ "body": body_content,
116
+ "has_attachments": len(data.get("attachmentList", [])) > 0,
117
+ "attachments": [
118
+ {"filename": att.get("filename"), "size": att.get("size")}
119
+ for att in data.get("attachmentList", [])
120
+ ]
121
+ }, indent=2)
122
+
123
+ def _read_outlook_message(self, execute_tool) -> str:
124
+ """Reads an Outlook message by ID."""
125
+ result = execute_tool(
126
+ tool_name="OUTLOOK_GET_MESSAGE",
127
+ arguments={
128
+ "user_id": "me",
129
+ "message_id": self.message_id,
130
+ "select": [
131
+ "id", "subject", "from", "toRecipients", "ccRecipients",
132
+ "receivedDateTime", "body", "hasAttachments", "webLink",
133
+ "isRead", "importance", "conversationId"
134
+ ]
135
+ },
136
+ )
137
+
138
+ if isinstance(result, dict) and result.get("error"):
139
+ return f"Error reading Outlook message: {result.get('error')}"
140
+
141
+ data = result.get("data", {})
142
+
143
+ if not data:
144
+ return f"Message not found: {self.message_id}"
145
+
146
+ from_info = data.get("from", {}).get("emailAddress", {})
147
+ from_str = f"{from_info.get('name', '')} <{from_info.get('address', 'Unknown')}>"
148
+
149
+ to_recipients = []
150
+ for recipient in data.get("toRecipients", []):
151
+ email_addr = recipient.get("emailAddress", {})
152
+ to_recipients.append(f"{email_addr.get('name', '')} <{email_addr.get('address', '')}>")
153
+
154
+ cc_recipients = []
155
+ for recipient in data.get("ccRecipients", []):
156
+ email_addr = recipient.get("emailAddress", {})
157
+ cc_recipients.append(f"{email_addr.get('name', '')} <{email_addr.get('address', '')}>")
158
+
159
+ body_data = data.get("body", {})
160
+ body_content = body_data.get("content", "")
161
+ body_type = body_data.get("contentType", "text")
162
+
163
+ if self.body_format == "text" and body_type.lower() == "html" and body_content:
164
+ body_content = strip_html(body_content)
165
+
166
+ return json.dumps({
167
+ "provider": "outlook",
168
+ "message_id": data.get("id"),
169
+ "conversation_id": data.get("conversationId"),
170
+ "subject": data.get("subject", "(No subject)"),
171
+ "from": from_str,
172
+ "to": to_recipients,
173
+ "cc": cc_recipients,
174
+ "received_at": data.get("receivedDateTime"),
175
+ "is_read": data.get("isRead"),
176
+ "importance": data.get("importance"),
177
+ "body": body_content,
178
+ "has_attachments": data.get("hasAttachments", False),
179
+ "web_link": data.get("webLink", "")
180
+ }, indent=2)
181
+
182
+
183
+ if __name__ == "__main__":
184
+ import sys
185
+ import os
186
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
187
+
188
+ print("=" * 60)
189
+ print("ReadEmail Test Suite")
190
+ print("=" * 60)
191
+ print()
192
+
193
+ # Get a message ID from CheckUnreadEmails
194
+ from virtual_assistant.tools.CheckUnreadEmails import CheckUnreadEmails
195
+ check_tool = CheckUnreadEmails(provider="gmail", limit=1)
196
+ result = check_tool.run()
197
+
198
+ import json
199
+ try:
200
+ data = json.loads(result)
201
+ if data.get("emails"):
202
+ message_id = data["emails"][0]["message_id"]
203
+
204
+ # Test 1: Read Gmail message as plain text (default)
205
+ print("Test 1: Gmail - Plain text (default)")
206
+ print("-" * 60)
207
+ tool = ReadEmail(provider="gmail", message_id=message_id, body_format="text")
208
+ result = tool.run()
209
+ result_data = json.loads(result)
210
+ if result_data.get("body") and len(result_data["body"]) > 500:
211
+ result_data["body"] = result_data["body"][:500] + "... [truncated]"
212
+ print(json.dumps(result_data, indent=2))
213
+ print()
214
+
215
+ # Test 2: Read Gmail message as HTML
216
+ print("Test 2: Gmail - HTML format")
217
+ print("-" * 60)
218
+ tool = ReadEmail(provider="gmail", message_id=message_id, body_format="html")
219
+ result = tool.run()
220
+ result_data = json.loads(result)
221
+ if result_data.get("body") and len(result_data["body"]) > 300:
222
+ result_data["body"] = result_data["body"][:300] + "... [truncated]"
223
+ print(json.dumps(result_data, indent=2))
224
+ print()
225
+ else:
226
+ print("No unread emails to test with")
227
+ except json.JSONDecodeError:
228
+ print(f"Error parsing result: {result}")
229
+
230
+ # Test 3: Read an Outlook message
231
+ print("Test 3: Outlook - Plain text")
232
+ print("-" * 60)
233
+ check_tool = CheckUnreadEmails(provider="outlook", limit=1)
234
+ result = check_tool.run()
235
+ try:
236
+ data = json.loads(result)
237
+ if data.get("emails"):
238
+ message_id = data["emails"][0]["message_id"]
239
+ tool = ReadEmail(provider="outlook", message_id=message_id, body_format="text")
240
+ result = tool.run()
241
+ print(result[:1500])
242
+ else:
243
+ print("No unread Outlook emails to test with")
244
+ except json.JSONDecodeError:
245
+ print(f"Result: {result}")
246
+ print()
247
+
248
+ print("=" * 60)
249
+ print("All tests completed!")
250
+ print("=" * 60)
251
+
@@ -0,0 +1,108 @@
1
+ import mimetypes
2
+ import os
3
+ from typing import Optional
4
+
5
+ from agency_swarm.tools import BaseTool
6
+ from pydantic import Field
7
+
8
+
9
+ class ReadFile(BaseTool):
10
+ """
11
+ Reads a file from the local filesystem.
12
+ Use this tool to read file contents before editing or to understand existing code.
13
+
14
+ Usage:
15
+ - The file_path parameter must be an absolute path
16
+ - By default, it reads up to 2000 lines starting from the beginning
17
+ - You can optionally specify a line offset and limit for long files
18
+ - Results are returned with line numbers in cat -n format
19
+ """
20
+
21
+ file_path: str = Field(..., description="The absolute path to the file to read")
22
+ offset: Optional[int] = Field(
23
+ None,
24
+ description="The line number to start reading from. Only provide if the file is too large to read at once",
25
+ )
26
+ limit: Optional[int] = Field(
27
+ None,
28
+ description="The number of lines to read. Only provide if the file is too large to read at once.",
29
+ )
30
+
31
+ def run(self):
32
+ try:
33
+ abs_path = os.path.abspath(self.file_path)
34
+ try:
35
+ if hasattr(self, '_context') and self._context is not None:
36
+ read_files = self._context.get("read_files", set())
37
+ read_files.add(abs_path)
38
+ self._context.set("read_files", read_files)
39
+ except (AttributeError, TypeError):
40
+ # Context not available in standalone test mode
41
+ pass
42
+
43
+ if not os.path.exists(self.file_path):
44
+ return f"Error: File does not exist: {self.file_path}"
45
+
46
+ if not os.path.isfile(self.file_path):
47
+ return f"Error: Path is not a file: {self.file_path}"
48
+
49
+ mime_type, _ = mimetypes.guess_type(self.file_path)
50
+ if mime_type and mime_type.startswith("image/"):
51
+ return f"[IMAGE FILE: {self.file_path}]\nThis is an image file ({mime_type}). In a multimodal environment, the image content would be displayed visually."
52
+
53
+ if self.file_path.endswith(".ipynb"):
54
+ return "Error: This is a Jupyter notebook file. Please use a notebook-specific tool instead."
55
+
56
+ try:
57
+ with open(self.file_path, "r", encoding="utf-8") as file:
58
+ lines = file.readlines()
59
+ except UnicodeDecodeError:
60
+ try:
61
+ with open(self.file_path, "r", encoding="latin-1") as file:
62
+ lines = file.readlines()
63
+ except UnicodeDecodeError:
64
+ return f"Error: Unable to decode file {self.file_path}. It may be a binary file."
65
+
66
+ if not lines:
67
+ return f"Warning: File exists but has empty contents: {self.file_path}"
68
+
69
+ start_line = (self.offset - 1) if self.offset else 0
70
+ start_line = max(0, start_line)
71
+
72
+ if self.limit:
73
+ end_line = start_line + self.limit
74
+ selected_lines = lines[start_line:end_line]
75
+ else:
76
+ selected_lines = lines[start_line : start_line + 2000]
77
+
78
+ result_lines = []
79
+ for i, line in enumerate(selected_lines, start=start_line + 1):
80
+ if len(line) > 2000:
81
+ line = line[:1997] + "...\n"
82
+ result_lines.append(f"{i:>6}\t{line.rstrip()}\n")
83
+ result = "".join(result_lines)
84
+
85
+ total_lines = len(lines)
86
+ lines_shown = len(selected_lines)
87
+
88
+ if lines_shown < total_lines:
89
+ if self.offset or self.limit:
90
+ result += f"\n[Truncated: showing lines {start_line + 1}-{start_line + lines_shown} of {total_lines} total lines]"
91
+ else:
92
+ result += f"\n[Truncated: showing first {lines_shown} of {total_lines} total lines]"
93
+
94
+ return result.rstrip()
95
+
96
+ except PermissionError:
97
+ return f"Error: Permission denied reading file: {self.file_path}"
98
+ except Exception as e:
99
+ return f"Error reading file: {str(e)}"
100
+
101
+
102
+ if __name__ == "__main__":
103
+ # Test the tool with its own file
104
+ current_file = __file__
105
+ tool = ReadFile(file_path=current_file, limit=10)
106
+ print("Reading first 10 lines:")
107
+ print(tool.run())
108
+
@@ -0,0 +1,191 @@
1
+ from typing import Optional
2
+ from agency_swarm.tools import BaseTool
3
+ from pydantic import Field
4
+ import json
5
+ from datetime import datetime
6
+
7
+ from helpers import execute_composio_tool
8
+
9
+
10
+ class ReadSlackMessages(BaseTool):
11
+ """
12
+ Reads messages from a Slack channel or DM. Use channel ID (e.g., C06NX4Q1ACE)
13
+ or channel name (e.g., #general). For threads, provide the parent message timestamp.
14
+ """
15
+
16
+ channel: str = Field(
17
+ ...,
18
+ description="Channel ID or name (e.g., 'C06NX4Q1ACE' or '#general')"
19
+ )
20
+
21
+ limit: int = Field(
22
+ default=10,
23
+ ge=1,
24
+ le=50,
25
+ description="Number of messages to fetch (1-50). Default: 10"
26
+ )
27
+
28
+ thread_ts: Optional[str] = Field(
29
+ default=None,
30
+ description="Thread parent timestamp to read replies. Leave empty for main channel."
31
+ )
32
+
33
+ include_replies: bool = Field(
34
+ default=False,
35
+ description="Include thread replies for messages that have them. Default: False"
36
+ )
37
+
38
+ def run(self):
39
+ try:
40
+ # Resolve channel name to ID if needed
41
+ channel_id = self._resolve_channel(execute_composio_tool, self.channel)
42
+ if channel_id.startswith("Error"):
43
+ return channel_id
44
+
45
+ # Fetch messages
46
+ if self.thread_ts:
47
+ messages = self._fetch_thread(execute_composio_tool, channel_id)
48
+ else:
49
+ messages = self._fetch_channel(execute_composio_tool, channel_id)
50
+
51
+ if not messages:
52
+ return json.dumps({"count": 0, "messages": []}, indent=2)
53
+
54
+ # Format output
55
+ formatted = []
56
+ for msg in messages[:self.limit]:
57
+ formatted_msg = self._format_message(msg)
58
+
59
+ # Fetch thread replies if requested and message has replies
60
+ if self.include_replies and msg.get("reply_count", 0) > 0:
61
+ replies = self._fetch_thread_replies(execute_composio_tool, channel_id, msg.get("ts"))
62
+ if replies:
63
+ formatted_msg["thread"] = [self._format_message(r) for r in replies]
64
+
65
+ formatted.append(formatted_msg)
66
+
67
+ return json.dumps({
68
+ "count": len(formatted),
69
+ "channel_id": channel_id,
70
+ "messages": formatted
71
+ }, indent=2)
72
+
73
+ except Exception as e:
74
+ return f"Error: {str(e)}"
75
+
76
+ def _resolve_channel(self, execute_tool, channel: str) -> str:
77
+ """Resolves channel name to ID."""
78
+ # Already an ID
79
+ if channel.startswith(("C", "D", "G")):
80
+ return channel
81
+
82
+ # Remove # prefix
83
+ name = channel.lstrip("#")
84
+
85
+ # Search for channel
86
+ result = execute_tool(
87
+ tool_name="SLACK_FIND_CHANNELS",
88
+ arguments={"query": name},
89
+ )
90
+
91
+ if result.get("error"):
92
+ return f"Error: Could not find channel '{name}'"
93
+
94
+ channels = result.get("data", {}).get("channels", [])
95
+ for ch in channels:
96
+ if ch.get("name") == name:
97
+ return ch.get("id")
98
+
99
+ return f"Error: Channel '{name}' not found"
100
+
101
+ def _fetch_channel(self, execute_tool, channel_id: str) -> list:
102
+ """Fetches messages from channel."""
103
+ result = execute_tool(
104
+ tool_name="SLACK_FETCH_CONVERSATION_HISTORY",
105
+ arguments={
106
+ "channel": channel_id,
107
+ "limit": self.limit
108
+ },
109
+ )
110
+
111
+ if result.get("error"):
112
+ return []
113
+
114
+ return result.get("data", {}).get("messages", [])
115
+
116
+ def _fetch_thread(self, execute_tool, channel_id: str) -> list:
117
+ """Fetches thread replies for thread_ts parameter."""
118
+ result = execute_tool(
119
+ tool_name="SLACK_FETCH_MESSAGE_THREAD_FROM_A_CONVERSATION",
120
+ arguments={
121
+ "channel": channel_id,
122
+ "ts": self.thread_ts,
123
+ "limit": self.limit
124
+ },
125
+ )
126
+
127
+ if result.get("error"):
128
+ return []
129
+
130
+ return result.get("data", {}).get("messages", [])
131
+
132
+ def _fetch_thread_replies(self, execute_tool, channel_id: str, parent_ts: str) -> list:
133
+ """Fetches thread replies for a specific message."""
134
+ result = execute_tool(
135
+ tool_name="SLACK_FETCH_MESSAGE_THREAD_FROM_A_CONVERSATION",
136
+ arguments={
137
+ "channel": channel_id,
138
+ "ts": parent_ts,
139
+ "limit": 20
140
+ },
141
+ )
142
+
143
+ if result.get("error"):
144
+ return []
145
+
146
+ messages = result.get("data", {}).get("messages", [])
147
+ # Skip the parent message (first one), return only replies
148
+ return messages[1:] if len(messages) > 1 else []
149
+
150
+ def _format_message(self, msg: dict) -> dict:
151
+ """Formats message for output."""
152
+ files = msg.get("files", [])
153
+ attachments = [f.get("name") or "file" for f in files] if files else None
154
+
155
+ formatted = {
156
+ "sender": msg.get("user", msg.get("bot_id", "unknown")),
157
+ "text": msg.get("text", ""),
158
+ "ts": msg.get("ts"),
159
+ "time": self._format_ts(msg.get("ts"))
160
+ }
161
+
162
+ if msg.get("reply_count"):
163
+ formatted["replies"] = msg.get("reply_count")
164
+
165
+ if attachments:
166
+ formatted["attachments"] = attachments
167
+
168
+ return formatted
169
+
170
+ def _format_ts(self, ts: str) -> str:
171
+ """Formats timestamp."""
172
+ if not ts:
173
+ return ""
174
+ try:
175
+ return datetime.fromtimestamp(float(ts.split(".")[0])).strftime("%Y-%m-%d %H:%M")
176
+ except (ValueError, TypeError):
177
+ return ts
178
+
179
+
180
+ if __name__ == "__main__":
181
+ import sys
182
+ import os
183
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
184
+
185
+ print("ReadSlackMessages Test")
186
+ print("-" * 40)
187
+
188
+ # Test reading from aaas-gains-ai with replies
189
+ tool = ReadSlackMessages(channel="#aaas-gains-ai", limit=3, include_replies=True)
190
+ print(tool.run())
191
+
@@ -0,0 +1,137 @@
1
+ from typing import Literal, List
2
+ from agency_swarm.tools import BaseTool
3
+ from pydantic import Field
4
+ import json
5
+
6
+ from helpers import execute_composio_tool
7
+
8
+
9
+ class RemoveLabelFromEmail(BaseTool):
10
+ """
11
+ Removes labels from a specific email message.
12
+
13
+ For Gmail: Use label IDs. Common operations:
14
+ - Remove 'UNREAD' to mark as read
15
+ - Remove 'INBOX' to archive
16
+ - Remove 'STARRED' to unstar
17
+
18
+ For Outlook: Remove category names from the message.
19
+ """
20
+
21
+ provider: Literal["gmail", "outlook"] = Field(
22
+ ...,
23
+ description="Email provider: 'gmail' or 'outlook'"
24
+ )
25
+
26
+ message_id: str = Field(
27
+ ...,
28
+ description="The message ID to remove labels from (obtained from CheckUnreadEmails)"
29
+ )
30
+
31
+ label_ids: List[str] = Field(
32
+ ...,
33
+ description="List of label IDs to remove. Gmail: 'UNREAD', 'INBOX', 'STARRED', 'Label_123'. Outlook: category names."
34
+ )
35
+
36
+ def run(self):
37
+ try:
38
+ if self.provider == "gmail":
39
+ return self._remove_gmail_labels(execute_composio_tool)
40
+ else:
41
+ return self._remove_outlook_categories(execute_composio_tool)
42
+
43
+ except Exception as e:
44
+ return f"Error removing labels: {str(e)}"
45
+
46
+ def _remove_gmail_labels(self, execute_tool) -> str:
47
+ """Removes labels from a Gmail message."""
48
+ result = execute_tool(
49
+ tool_name="GMAIL_ADD_LABEL_TO_EMAIL",
50
+ arguments={
51
+ "user_id": "me",
52
+ "message_id": self.message_id,
53
+ "remove_label_ids": self.label_ids
54
+ },
55
+ )
56
+
57
+ if isinstance(result, dict) and result.get("error"):
58
+ return f"Error removing Gmail labels: {result.get('error')}"
59
+
60
+ data = result.get("data", {})
61
+
62
+ return json.dumps({
63
+ "provider": "gmail",
64
+ "success": True,
65
+ "message_id": self.message_id,
66
+ "labels_removed": self.label_ids,
67
+ "current_labels": data.get("labelIds", [])
68
+ }, indent=2)
69
+
70
+ def _remove_outlook_categories(self, execute_tool) -> str:
71
+ """Removes categories from an Outlook message."""
72
+ # Get current categories
73
+ get_result = execute_tool(
74
+ tool_name="OUTLOOK_GET_MESSAGE",
75
+ arguments={
76
+ "user_id": "me",
77
+ "message_id": self.message_id,
78
+ "select": ["id", "categories"]
79
+ },
80
+ )
81
+
82
+ if isinstance(get_result, dict) and get_result.get("error"):
83
+ return f"Error getting Outlook message: {get_result.get('error')}"
84
+
85
+ current_categories = get_result.get("data", {}).get("categories", [])
86
+
87
+ # Calculate remaining categories
88
+ remaining_categories = [c for c in current_categories if c not in self.label_ids]
89
+
90
+ return json.dumps({
91
+ "provider": "outlook",
92
+ "success": True,
93
+ "message_id": self.message_id,
94
+ "categories_removed": self.label_ids,
95
+ "remaining_categories": remaining_categories,
96
+ "note": "Outlook category update requires OUTLOOK_UPDATE_MESSAGE which may not be available"
97
+ }, indent=2)
98
+
99
+
100
+ if __name__ == "__main__":
101
+ import sys
102
+ import os
103
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
104
+
105
+ print("=" * 60)
106
+ print("RemoveLabelFromEmail Test Suite")
107
+ print("=" * 60)
108
+ print()
109
+
110
+ # Get a message ID first
111
+ from virtual_assistant.tools.FindEmails import FindEmails
112
+ check_tool = FindEmails(provider="gmail", query="is:unread", limit=1)
113
+ result = check_tool.run()
114
+
115
+ import json
116
+ data = json.loads(result)
117
+ if data.get("emails"):
118
+ message_id = data["emails"][0]["message_id"]
119
+
120
+ # Test: Mark as read by removing UNREAD label
121
+ print("Test: Mark email as read (remove UNREAD label)")
122
+ print("-" * 60)
123
+ tool = RemoveLabelFromEmail(
124
+ provider="gmail",
125
+ message_id=message_id,
126
+ label_ids=["UNREAD"]
127
+ )
128
+ result = tool.run()
129
+ print(result)
130
+ else:
131
+ print("No emails to test with")
132
+
133
+ print()
134
+ print("=" * 60)
135
+ print("Tests completed!")
136
+ print("=" * 60)
137
+