@_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.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/bin/openswarm.js +38 -0
- package/config.py +34 -0
- package/data_analyst_agent/.cursor/rules/data_analyst.mdc +43 -0
- package/data_analyst_agent/__init__.py +3 -0
- package/data_analyst_agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/data_analyst_agent/__pycache__/data_analyst_agent.cpython-312.pyc +0 -0
- package/data_analyst_agent/data_analyst_agent.py +45 -0
- package/data_analyst_agent/instructions.md +173 -0
- package/data_analyst_agent/test_files/test_file.csv +21 -0
- package/data_analyst_agent/tools/__init__.py +6 -0
- package/deep_research/__init__.py +1 -0
- package/deep_research/__pycache__/__init__.cpython-312.pyc +0 -0
- package/deep_research/__pycache__/deep_research.cpython-312.pyc +0 -0
- package/deep_research/deep_research.py +27 -0
- package/deep_research/instructions.md +104 -0
- package/deep_research/tools/__init__.py +1 -0
- package/docs_agent/__init__.py +3 -0
- package/docs_agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/docs_agent/__pycache__/docs_agent.cpython-312.pyc +0 -0
- package/docs_agent/docs_agent.py +61 -0
- package/docs_agent/instructions.md +418 -0
- package/docs_agent/tools/ConvertDocument.py +323 -0
- package/docs_agent/tools/CreateDocument.py +287 -0
- package/docs_agent/tools/ListDocuments.py +134 -0
- package/docs_agent/tools/ModifyDocument.py +247 -0
- package/docs_agent/tools/RestoreDocument.py +79 -0
- package/docs_agent/tools/ViewDocument.py +153 -0
- package/docs_agent/tools/__init__.py +1 -0
- package/docs_agent/tools/__pycache__/ConvertDocument.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/CreateDocument.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/ListDocuments.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/ModifyDocument.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/RestoreDocument.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/ViewDocument.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__init__.py +1 -0
- package/docs_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/doc_file_utils.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_blocks.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_constants.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_core.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_css.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_images.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_page.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_paragraphs.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_playwright.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_selectors.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_shared.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_validation.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/doc_file_utils.py +29 -0
- package/docs_agent/tools/utils/html_docx_blocks.py +262 -0
- package/docs_agent/tools/utils/html_docx_constants.py +78 -0
- package/docs_agent/tools/utils/html_docx_core.py +138 -0
- package/docs_agent/tools/utils/html_docx_css.py +262 -0
- package/docs_agent/tools/utils/html_docx_images.py +293 -0
- package/docs_agent/tools/utils/html_docx_page.py +185 -0
- package/docs_agent/tools/utils/html_docx_paragraphs.py +342 -0
- package/docs_agent/tools/utils/html_docx_playwright.py +184 -0
- package/docs_agent/tools/utils/html_docx_selectors.py +196 -0
- package/docs_agent/tools/utils/html_docx_shared.py +23 -0
- package/docs_agent/tools/utils/html_docx_tables.py +942 -0
- package/docs_agent/tools/utils/html_validation.py +102 -0
- package/helpers.py +59 -0
- package/image_generation_agent/__init__.py +1 -0
- package/image_generation_agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/image_generation_agent/__pycache__/image_generation_agent.cpython-312.pyc +0 -0
- package/image_generation_agent/image_generation_agent.py +31 -0
- package/image_generation_agent/instructions.md +80 -0
- package/image_generation_agent/tools/CombineImages.py +211 -0
- package/image_generation_agent/tools/EditImages.py +200 -0
- package/image_generation_agent/tools/GenerateImages.py +184 -0
- package/image_generation_agent/tools/RemoveBackground.py +108 -0
- package/image_generation_agent/tools/__init__.py +2 -0
- package/image_generation_agent/tools/__pycache__/CombineImages.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/__pycache__/EditImages.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/__pycache__/GenerateImages.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/__pycache__/RemoveBackground.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/utils/__init__.py +2 -0
- package/image_generation_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/utils/__pycache__/image_io.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/utils/image_io.py +308 -0
- package/onboard.py +325 -0
- package/orchestrator/__init__.py +3 -0
- package/orchestrator/__pycache__/__init__.cpython-312.pyc +0 -0
- package/orchestrator/__pycache__/orchestrator.cpython-312.pyc +0 -0
- package/orchestrator/instructions.md +90 -0
- package/orchestrator/orchestrator.py +33 -0
- package/package.json +49 -0
- package/patches/__init__.py +1 -0
- package/patches/__pycache__/__init__.cpython-312.pyc +0 -0
- package/patches/__pycache__/patch_agency_swarm_dual_comms.cpython-312.pyc +0 -0
- package/patches/__pycache__/patch_file_attachment_refs.cpython-312.pyc +0 -0
- package/patches/__pycache__/patch_ipython_interpreter_composio.cpython-312.pyc +0 -0
- package/patches/dom-to-pptx+1.1.5.patch +133440 -0
- package/patches/patch_agency_swarm_dual_comms.py +199 -0
- package/patches/patch_file_attachment_refs.py +74 -0
- package/patches/patch_ipython_interpreter_composio.py +54 -0
- package/pyproject.toml +67 -0
- package/run.py +343 -0
- package/server.py +26 -0
- package/shared_instructions.md +119 -0
- package/shared_tools/CopyFile.py +68 -0
- package/shared_tools/ExecuteTool.py +184 -0
- package/shared_tools/FindTools.py +101 -0
- package/shared_tools/ManageConnections.py +43 -0
- package/shared_tools/SearchTools.py +44 -0
- package/shared_tools/__init__.py +7 -0
- package/shared_tools/__pycache__/CopyFile.cpython-312.pyc +0 -0
- package/shared_tools/__pycache__/ExecuteTool.cpython-312.pyc +0 -0
- package/shared_tools/__pycache__/FindTools.cpython-312.pyc +0 -0
- package/shared_tools/__pycache__/ManageConnections.cpython-312.pyc +0 -0
- package/shared_tools/__pycache__/SearchTools.cpython-312.pyc +0 -0
- package/shared_tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/slides_agent/.cursor/rules/slides-agent-workflow.mdc +9 -0
- package/slides_agent/__init__.py +1 -0
- package/slides_agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/slides_agent/__pycache__/slides_agent.cpython-312.pyc +0 -0
- package/slides_agent/instructions.md +298 -0
- package/slides_agent/pptx/SKILL.md +528 -0
- package/slides_agent/pptx/html2pptx.md +625 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/slides_agent/pptx/ooxml/schemas/mce/mc.xsd +75 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/slides_agent/pptx/ooxml/scripts/pack.py +169 -0
- package/slides_agent/pptx/ooxml/scripts/unpack.py +29 -0
- package/slides_agent/pptx/ooxml/scripts/validate.py +69 -0
- package/slides_agent/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/slides_agent/pptx/ooxml/scripts/validation/base.py +951 -0
- package/slides_agent/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/slides_agent/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/slides_agent/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/slides_agent/pptx/ooxml.md +427 -0
- package/slides_agent/pptx/scripts/html2pptx.js +1092 -0
- package/slides_agent/pptx/scripts/inventory.py +1020 -0
- package/slides_agent/pptx/scripts/rearrange.py +231 -0
- package/slides_agent/pptx/scripts/replace.py +385 -0
- package/slides_agent/pptx/scripts/thumbnail.py +451 -0
- package/slides_agent/slides_agent.py +109 -0
- package/slides_agent/test_deck/_theme.css +92 -0
- package/slides_agent/test_deck/assets/placeholder.svg +11 -0
- package/slides_agent/test_deck/slide_01_title.html +10 -0
- package/slides_agent/test_deck/slide_02_image_split.html +23 -0
- package/slides_agent/test_deck/slide_03_kpi.html +21 -0
- package/slides_agent/tools/ApplyPptxTextReplacements.py +91 -0
- package/slides_agent/tools/BuildPptxFromHtmlSlides.py +221 -0
- package/slides_agent/tools/CheckSlide.py +218 -0
- package/slides_agent/tools/CheckSlideCanvasOverflow.py +221 -0
- package/slides_agent/tools/CreateImageMontage.py +261 -0
- package/slides_agent/tools/CreatePptxThumbnailGrid.py +168 -0
- package/slides_agent/tools/DeleteSlide.py +78 -0
- package/slides_agent/tools/DownloadImage.py +79 -0
- package/slides_agent/tools/EnsureRasterImage.py +157 -0
- package/slides_agent/tools/ExtractPptxTextInventory.py +104 -0
- package/slides_agent/tools/GenerateImage.py +189 -0
- package/slides_agent/tools/ImageSearch.py +127 -0
- package/slides_agent/tools/InsertNewSlides.py +393 -0
- package/slides_agent/tools/ManageTheme.py +112 -0
- package/slides_agent/tools/ModifySlide.py +563 -0
- package/slides_agent/tools/ReadSlide.py +26 -0
- package/slides_agent/tools/RearrangePptxSlidesFromTemplate.py +114 -0
- package/slides_agent/tools/RestoreSnapshot.py +89 -0
- package/slides_agent/tools/SlideScreenshot.py +66 -0
- package/slides_agent/tools/__init__.py +54 -0
- package/slides_agent/tools/__pycache__/ApplyPptxTextReplacements.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/BuildPptxFromHtmlSlides.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/CheckSlide.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/CheckSlideCanvasOverflow.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/CreateImageMontage.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/CreatePptxThumbnailGrid.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/DeleteSlide.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/DownloadImage.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/EnsureRasterImage.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/ExtractPptxTextInventory.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/GenerateImage.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/ImageSearch.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/InsertNewSlides.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/ManageTheme.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/ModifySlide.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/ReadSlide.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/RearrangePptxSlidesFromTemplate.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/RestoreSnapshot.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/SlideScreenshot.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/slide_file_utils.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/slide_html_utils.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/template_registry.cpython-312.pyc +0 -0
- package/slides_agent/tools/deck_utils.py +31 -0
- package/slides_agent/tools/html2pptx_runner.js +1183 -0
- package/slides_agent/tools/html_writer_instructions.md +149 -0
- package/slides_agent/tools/slide_file_utils.py +108 -0
- package/slides_agent/tools/slide_html_utils.py +354 -0
- package/slides_agent/tools/template_registry.py +55 -0
- package/swarm.py +82 -0
- package/video_generation_agent/__init__.py +1 -0
- package/video_generation_agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/video_generation_agent/__pycache__/video_generation_agent.cpython-312.pyc +0 -0
- package/video_generation_agent/instructions.md +178 -0
- package/video_generation_agent/tools/AddSubtitles.py +425 -0
- package/video_generation_agent/tools/CombineImages.py +166 -0
- package/video_generation_agent/tools/CombineVideos.py +113 -0
- package/video_generation_agent/tools/EditAudio.py +297 -0
- package/video_generation_agent/tools/EditImage.py +144 -0
- package/video_generation_agent/tools/EditVideoContent.py +369 -0
- package/video_generation_agent/tools/GenerateImage.py +133 -0
- package/video_generation_agent/tools/GenerateVideo.py +556 -0
- package/video_generation_agent/tools/TrimVideo.py +233 -0
- package/video_generation_agent/tools/__init__.py +1 -0
- package/video_generation_agent/tools/__pycache__/AddSubtitles.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/CombineImages.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/CombineVideos.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/EditAudio.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/EditImage.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/EditVideoContent.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/GenerateImage.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/GenerateVideo.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/TrimVideo.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/utils/__init__.py +1 -0
- package/video_generation_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/utils/__pycache__/image_utils.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/utils/__pycache__/video_utils.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/utils/image_utils.py +174 -0
- package/video_generation_agent/tools/utils/video_utils.py +522 -0
- package/video_generation_agent/video_generation_agent.py +26 -0
- package/virtual_assistant/__init__.py +1 -0
- package/virtual_assistant/__pycache__/__init__.cpython-312.pyc +0 -0
- package/virtual_assistant/__pycache__/virtual_assistant.cpython-312.pyc +0 -0
- package/virtual_assistant/instructions.md +206 -0
- package/virtual_assistant/tools/AddLabelToEmail.py +154 -0
- package/virtual_assistant/tools/CheckEventsForDate.py +218 -0
- package/virtual_assistant/tools/CheckUnreadSlackMessages.py +216 -0
- package/virtual_assistant/tools/CreateCalendarEvent.py +261 -0
- package/virtual_assistant/tools/DeleteCalendarEvent.py +137 -0
- package/virtual_assistant/tools/DeleteDraft.py +95 -0
- package/virtual_assistant/tools/DraftEmail.py +239 -0
- package/virtual_assistant/tools/EditFile.py +113 -0
- package/virtual_assistant/tools/FindEmails.py +330 -0
- package/virtual_assistant/tools/GetCurrentTime.py +69 -0
- package/virtual_assistant/tools/GetSlackUserInfo.py +117 -0
- package/virtual_assistant/tools/ListDirectory.py +113 -0
- package/virtual_assistant/tools/ListSkills.py +94 -0
- package/virtual_assistant/tools/ManageLabels.py +295 -0
- package/virtual_assistant/tools/ProductSearch.py +254 -0
- package/virtual_assistant/tools/ReadEmail.py +251 -0
- package/virtual_assistant/tools/ReadFile.py +108 -0
- package/virtual_assistant/tools/ReadSlackMessages.py +191 -0
- package/virtual_assistant/tools/RemoveLabelFromEmail.py +137 -0
- package/virtual_assistant/tools/RescheduleCalendarEvent.py +227 -0
- package/virtual_assistant/tools/ScholarSearch.py +216 -0
- package/virtual_assistant/tools/SendDraft.py +101 -0
- package/virtual_assistant/tools/SendSlackMessage.py +148 -0
- package/virtual_assistant/tools/WriteFile.py +95 -0
- package/virtual_assistant/tools/__init__.py +1 -0
- package/virtual_assistant/tools/__pycache__/AddLabelToEmail.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/CheckEventsForDate.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/CheckUnreadSlackMessages.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/CreateCalendarEvent.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/DeleteCalendarEvent.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/DeleteDraft.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/DraftEmail.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/EditFile.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/FindEmails.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/GetCurrentTime.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/GetSlackUserInfo.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ListDirectory.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ListSkills.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ManageLabels.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ProductSearch.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ReadEmail.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ReadFile.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ReadSlackMessages.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/RemoveLabelFromEmail.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/RescheduleCalendarEvent.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ScholarSearch.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/SendDraft.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/SendSlackMessage.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/WriteFile.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- 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(' ', ' ')
|
|
29
|
+
text = text.replace('&', '&')
|
|
30
|
+
text = text.replace('<', '<')
|
|
31
|
+
text = text.replace('>', '>')
|
|
32
|
+
text = text.replace('"', '"')
|
|
33
|
+
text = text.replace(''', "'")
|
|
34
|
+
text = text.replace(''', "'")
|
|
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
|
+
|