@_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,227 @@
|
|
|
1
|
+
from typing import Literal, Optional
|
|
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 RescheduleCalendarEvent(BaseTool):
|
|
10
|
+
"""
|
|
11
|
+
Reschedules an existing calendar event to a new date/time.
|
|
12
|
+
|
|
13
|
+
Supports both Google Calendar and Outlook. Can update:
|
|
14
|
+
- Start and end times
|
|
15
|
+
- Event title
|
|
16
|
+
- Location
|
|
17
|
+
- Description
|
|
18
|
+
|
|
19
|
+
Use CheckEventsForDate first to get the event_id of the event to reschedule.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
provider: Literal["google", "outlook"] = Field(
|
|
23
|
+
...,
|
|
24
|
+
description="Calendar provider: 'google' or 'outlook'"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
event_id: str = Field(
|
|
28
|
+
...,
|
|
29
|
+
description="The unique ID of the event to reschedule (from CheckEventsForDate)"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
new_start_datetime: Optional[str] = Field(
|
|
33
|
+
default=None,
|
|
34
|
+
description="New start date/time in ISO format (e.g., '2026-01-10T14:00:00'). Required for rescheduling."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
new_end_datetime: Optional[str] = Field(
|
|
38
|
+
default=None,
|
|
39
|
+
description="New end date/time in ISO format. If not provided, will be calculated based on original duration."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
timezone: Optional[str] = Field(
|
|
43
|
+
default=None,
|
|
44
|
+
description="IANA timezone (e.g., 'America/New_York', 'Asia/Dubai'). Uses event's current timezone if not specified."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
new_title: Optional[str] = Field(
|
|
48
|
+
default=None,
|
|
49
|
+
description="New title/subject for the event. Leave empty to keep current title."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
new_location: Optional[str] = Field(
|
|
53
|
+
default=None,
|
|
54
|
+
description="New location for the event. Leave empty to keep current location."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
new_description: Optional[str] = Field(
|
|
58
|
+
default=None,
|
|
59
|
+
description="New description for the event. Leave empty to keep current description."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
send_updates: Literal["all", "externalOnly", "none"] = Field(
|
|
63
|
+
default="all",
|
|
64
|
+
description="Who to notify about the change: 'all', 'externalOnly', or 'none'"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def run(self):
|
|
68
|
+
try:
|
|
69
|
+
if self.provider == "google":
|
|
70
|
+
return self._reschedule_google_event(execute_composio_tool)
|
|
71
|
+
else:
|
|
72
|
+
return self._reschedule_outlook_event(execute_composio_tool)
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
return f"Error rescheduling event: {str(e)}"
|
|
76
|
+
|
|
77
|
+
def _reschedule_google_event(self, execute_tool) -> str:
|
|
78
|
+
"""Reschedules a Google Calendar event."""
|
|
79
|
+
# Build arguments - only include fields that are being updated
|
|
80
|
+
arguments = {
|
|
81
|
+
"calendar_id": "primary",
|
|
82
|
+
"event_id": self.event_id
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if self.new_start_datetime:
|
|
86
|
+
arguments["start_time"] = self.new_start_datetime
|
|
87
|
+
|
|
88
|
+
if self.new_end_datetime:
|
|
89
|
+
arguments["end_time"] = self.new_end_datetime
|
|
90
|
+
|
|
91
|
+
if self.timezone:
|
|
92
|
+
arguments["timezone"] = self.timezone
|
|
93
|
+
|
|
94
|
+
if self.new_title:
|
|
95
|
+
arguments["summary"] = self.new_title
|
|
96
|
+
|
|
97
|
+
if self.new_location:
|
|
98
|
+
arguments["location"] = self.new_location
|
|
99
|
+
|
|
100
|
+
if self.new_description:
|
|
101
|
+
arguments["description"] = self.new_description
|
|
102
|
+
|
|
103
|
+
arguments["send_updates"] = self.send_updates
|
|
104
|
+
|
|
105
|
+
# Execute the patch
|
|
106
|
+
result = execute_tool(
|
|
107
|
+
tool_name="GOOGLECALENDAR_PATCH_EVENT",
|
|
108
|
+
arguments=arguments,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if isinstance(result, dict) and result.get("error"):
|
|
112
|
+
return f"Error rescheduling Google Calendar event: {result.get('error')}"
|
|
113
|
+
|
|
114
|
+
data = result.get("data", {})
|
|
115
|
+
|
|
116
|
+
# Extract updated event details
|
|
117
|
+
start = data.get("start", {})
|
|
118
|
+
end = data.get("end", {})
|
|
119
|
+
|
|
120
|
+
return json.dumps({
|
|
121
|
+
"provider": "google",
|
|
122
|
+
"success": True,
|
|
123
|
+
"event_id": data.get("id"),
|
|
124
|
+
"title": data.get("summary", "(No title)"),
|
|
125
|
+
"new_start": start.get("dateTime") or start.get("date"),
|
|
126
|
+
"new_end": end.get("dateTime") or end.get("date"),
|
|
127
|
+
"location": data.get("location", ""),
|
|
128
|
+
"html_link": data.get("htmlLink", ""),
|
|
129
|
+
"updates_sent_to": self.send_updates
|
|
130
|
+
}, indent=2)
|
|
131
|
+
|
|
132
|
+
def _reschedule_outlook_event(self, execute_tool) -> str:
|
|
133
|
+
"""Reschedules an Outlook calendar event."""
|
|
134
|
+
# Build arguments - only include fields that are being updated
|
|
135
|
+
arguments = {
|
|
136
|
+
"user_id": "me",
|
|
137
|
+
"event_id": self.event_id
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if self.new_start_datetime:
|
|
141
|
+
arguments["start_datetime"] = self.new_start_datetime
|
|
142
|
+
|
|
143
|
+
if self.new_end_datetime:
|
|
144
|
+
arguments["end_datetime"] = self.new_end_datetime
|
|
145
|
+
|
|
146
|
+
if self.timezone:
|
|
147
|
+
arguments["time_zone"] = self.timezone
|
|
148
|
+
|
|
149
|
+
if self.new_title:
|
|
150
|
+
arguments["subject"] = self.new_title
|
|
151
|
+
|
|
152
|
+
if self.new_location:
|
|
153
|
+
arguments["location"] = self.new_location
|
|
154
|
+
|
|
155
|
+
if self.new_description:
|
|
156
|
+
arguments["body"] = {
|
|
157
|
+
"contentType": "Text",
|
|
158
|
+
"content": self.new_description
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# Execute the update
|
|
162
|
+
result = execute_tool(
|
|
163
|
+
tool_name="OUTLOOK_UPDATE_CALENDAR_EVENT",
|
|
164
|
+
arguments=arguments,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if isinstance(result, dict) and result.get("error"):
|
|
168
|
+
return f"Error rescheduling Outlook event: {result.get('error')}"
|
|
169
|
+
|
|
170
|
+
data = result.get("data", {})
|
|
171
|
+
|
|
172
|
+
# Extract updated event details
|
|
173
|
+
start = data.get("start", {})
|
|
174
|
+
end = data.get("end", {})
|
|
175
|
+
|
|
176
|
+
return json.dumps({
|
|
177
|
+
"provider": "outlook",
|
|
178
|
+
"success": True,
|
|
179
|
+
"event_id": data.get("id"),
|
|
180
|
+
"title": data.get("subject", "(No title)"),
|
|
181
|
+
"new_start": start.get("dateTime"),
|
|
182
|
+
"new_end": end.get("dateTime"),
|
|
183
|
+
"location": data.get("location", {}).get("displayName", ""),
|
|
184
|
+
"web_link": data.get("webLink", "")
|
|
185
|
+
}, indent=2)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
if __name__ == "__main__":
|
|
189
|
+
import sys
|
|
190
|
+
import os
|
|
191
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
192
|
+
|
|
193
|
+
print("=" * 60)
|
|
194
|
+
print("RescheduleCalendarEvent Test Suite")
|
|
195
|
+
print("=" * 60)
|
|
196
|
+
print()
|
|
197
|
+
|
|
198
|
+
# First, get an event to reschedule
|
|
199
|
+
from virtual_assistant.tools.CheckEventsForDate import CheckEventsForDate
|
|
200
|
+
|
|
201
|
+
print("=== Getting events for tomorrow ===")
|
|
202
|
+
from datetime import datetime, timedelta
|
|
203
|
+
tomorrow = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
|
|
204
|
+
|
|
205
|
+
check_tool = CheckEventsForDate(provider="google", date=tomorrow)
|
|
206
|
+
result = check_tool.run()
|
|
207
|
+
print(result)
|
|
208
|
+
print()
|
|
209
|
+
|
|
210
|
+
import json
|
|
211
|
+
try:
|
|
212
|
+
data = json.loads(result)
|
|
213
|
+
if data.get("events"):
|
|
214
|
+
event = data["events"][0]
|
|
215
|
+
event_id = event["event_id"]
|
|
216
|
+
print(f"Found event: {event['title']} (ID: {event_id})")
|
|
217
|
+
else:
|
|
218
|
+
print("No events found for tomorrow to test with")
|
|
219
|
+
except json.JSONDecodeError:
|
|
220
|
+
print(f"Result: {result}")
|
|
221
|
+
|
|
222
|
+
print()
|
|
223
|
+
print("=" * 60)
|
|
224
|
+
print("Test completed!")
|
|
225
|
+
print("=" * 60)
|
|
226
|
+
|
|
227
|
+
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from agency_swarm.tools import BaseTool
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ScholarSearch(BaseTool):
|
|
10
|
+
"""
|
|
11
|
+
Searches for scholarly literature on Google Scholar.
|
|
12
|
+
|
|
13
|
+
Returns academic papers, articles, theses, books, and conference papers.
|
|
14
|
+
Includes links to PDFs and full-text resources when available.
|
|
15
|
+
|
|
16
|
+
RATE LIMIT: This tool can only be called ONCE per each user request (message) to save API costs.
|
|
17
|
+
Make sure to request enough results in a single call.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
query: str = Field(
|
|
21
|
+
...,
|
|
22
|
+
description="Search query for scholarly articles (e.g., 'machine learning', 'climate change effects', 'quantum computing')"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
year_from: Optional[int] = Field(
|
|
26
|
+
default=None,
|
|
27
|
+
description="Filter results from this year onwards (e.g., 2020)"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
year_to: Optional[int] = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
description="Filter results up to this year (e.g., 2024)"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
num_results: int = Field(
|
|
36
|
+
default=10,
|
|
37
|
+
ge=1,
|
|
38
|
+
le=20,
|
|
39
|
+
description="Number of results to return (1-20)"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
page: int = Field(
|
|
43
|
+
default=1,
|
|
44
|
+
ge=1,
|
|
45
|
+
description="Page number for pagination"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def run(self):
|
|
49
|
+
try:
|
|
50
|
+
import requests
|
|
51
|
+
|
|
52
|
+
# Rate limiting: Check if already called in this session
|
|
53
|
+
if self.context and self.context.get("scholar_search_called", False):
|
|
54
|
+
return "Error: ScholarSearch can only be called once per user request to save API costs. Use the results from the previous search or web search tool."
|
|
55
|
+
|
|
56
|
+
api_key = os.getenv("SEARCH_API_KEY")
|
|
57
|
+
if not api_key:
|
|
58
|
+
raise ValueError("SEARCH_API_KEY is not set. Add it to your .env to use ScholarSearch.")
|
|
59
|
+
|
|
60
|
+
# Build request parameters
|
|
61
|
+
params = {
|
|
62
|
+
"engine": "google_scholar",
|
|
63
|
+
"api_key": api_key,
|
|
64
|
+
"q": self.query,
|
|
65
|
+
"num": self.num_results,
|
|
66
|
+
"page": self.page
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Add year filters
|
|
70
|
+
if self.year_from:
|
|
71
|
+
params["as_ylo"] = self.year_from
|
|
72
|
+
|
|
73
|
+
if self.year_to:
|
|
74
|
+
params["as_yhi"] = self.year_to
|
|
75
|
+
|
|
76
|
+
# Make API request
|
|
77
|
+
response = requests.get(
|
|
78
|
+
"https://www.searchapi.io/api/v1/search",
|
|
79
|
+
params=params,
|
|
80
|
+
timeout=30
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if response.status_code != 200:
|
|
84
|
+
return f"Error: API returned status {response.status_code}: {response.text}"
|
|
85
|
+
|
|
86
|
+
data = response.json()
|
|
87
|
+
|
|
88
|
+
# Check for API errors
|
|
89
|
+
if "error" in data:
|
|
90
|
+
return f"Error from API: {data['error']}"
|
|
91
|
+
|
|
92
|
+
# Extract results
|
|
93
|
+
organic_results = data.get("organic_results", [])
|
|
94
|
+
profiles = data.get("profiles", [])
|
|
95
|
+
search_info = data.get("search_information", {})
|
|
96
|
+
|
|
97
|
+
# Format articles
|
|
98
|
+
articles = []
|
|
99
|
+
for result in organic_results:
|
|
100
|
+
# Extract authors
|
|
101
|
+
authors = []
|
|
102
|
+
for author in result.get("authors", []):
|
|
103
|
+
authors.append(author.get("name", "Unknown"))
|
|
104
|
+
|
|
105
|
+
# Extract citation info
|
|
106
|
+
inline_links = result.get("inline_links", {})
|
|
107
|
+
cited_by = inline_links.get("cited_by", {})
|
|
108
|
+
versions = inline_links.get("versions", {})
|
|
109
|
+
|
|
110
|
+
# Extract resource (PDF, etc.)
|
|
111
|
+
resource = result.get("resource", {})
|
|
112
|
+
|
|
113
|
+
article = {
|
|
114
|
+
"title": result.get("title"),
|
|
115
|
+
"link": result.get("link"),
|
|
116
|
+
"publication": result.get("publication"),
|
|
117
|
+
"snippet": result.get("snippet"),
|
|
118
|
+
"authors": authors,
|
|
119
|
+
"citations": cited_by.get("total"),
|
|
120
|
+
"cites_id": cited_by.get("cites_id"),
|
|
121
|
+
"versions_count": versions.get("total"),
|
|
122
|
+
"cluster_id": versions.get("cluster_id")
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Add resource link (PDF, etc.) - important for reading full papers
|
|
126
|
+
if resource:
|
|
127
|
+
article["resource"] = {
|
|
128
|
+
"name": resource.get("name"),
|
|
129
|
+
"format": resource.get("format"),
|
|
130
|
+
"link": resource.get("link")
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Add related links
|
|
134
|
+
if inline_links.get("related_articles_link"):
|
|
135
|
+
article["related_articles_link"] = inline_links.get("related_articles_link")
|
|
136
|
+
|
|
137
|
+
articles.append(article)
|
|
138
|
+
|
|
139
|
+
# Format author profiles if any
|
|
140
|
+
author_profiles = []
|
|
141
|
+
for profile in profiles:
|
|
142
|
+
author_profiles.append({
|
|
143
|
+
"name": profile.get("name"),
|
|
144
|
+
"affiliations": profile.get("affiliations"),
|
|
145
|
+
"email_domain": profile.get("email"),
|
|
146
|
+
"total_citations": profile.get("cited_by", {}).get("total"),
|
|
147
|
+
"profile_link": profile.get("link"),
|
|
148
|
+
"author_id": profile.get("author_id")
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
# Mark as called in shared state (rate limiting)
|
|
152
|
+
if self.context:
|
|
153
|
+
self.context.set("scholar_search_called", True)
|
|
154
|
+
|
|
155
|
+
result = {
|
|
156
|
+
"query": self.query,
|
|
157
|
+
"filters": {
|
|
158
|
+
"year_from": self.year_from,
|
|
159
|
+
"year_to": self.year_to
|
|
160
|
+
},
|
|
161
|
+
"total_results": search_info.get("total_results"),
|
|
162
|
+
"page": self.page,
|
|
163
|
+
"articles_count": len(articles),
|
|
164
|
+
"articles": articles
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if author_profiles:
|
|
168
|
+
result["author_profiles"] = author_profiles
|
|
169
|
+
|
|
170
|
+
return json.dumps(result, indent=2)
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
return f"Error searching scholar: {str(e)}"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__":
|
|
178
|
+
import sys
|
|
179
|
+
import os
|
|
180
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
181
|
+
|
|
182
|
+
print("=" * 60)
|
|
183
|
+
print("ScholarSearch Test Suite")
|
|
184
|
+
print("=" * 60)
|
|
185
|
+
print()
|
|
186
|
+
|
|
187
|
+
# Test 1: Basic search
|
|
188
|
+
print("Test 1: Basic scholarly search")
|
|
189
|
+
print("-" * 60)
|
|
190
|
+
tool = ScholarSearch(
|
|
191
|
+
query="transformer architecture deep learning",
|
|
192
|
+
num_results=5
|
|
193
|
+
)
|
|
194
|
+
result = tool.run()
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
data = json.loads(result)
|
|
198
|
+
print(f"Query: {data['query']}")
|
|
199
|
+
print(f"Total results: {data.get('total_results', 'N/A')}")
|
|
200
|
+
print(f"Articles returned: {data['articles_count']}")
|
|
201
|
+
print()
|
|
202
|
+
|
|
203
|
+
for i, article in enumerate(data['articles'][:3], 1):
|
|
204
|
+
print(f"{i}. {article['title']}")
|
|
205
|
+
print(f" Authors: {', '.join(article['authors'][:3])}...")
|
|
206
|
+
print(f" Citations: {article.get('citations', 'N/A')}")
|
|
207
|
+
if article.get('resource'):
|
|
208
|
+
print(f" PDF: {article['resource'].get('link', 'N/A')}")
|
|
209
|
+
print()
|
|
210
|
+
except json.JSONDecodeError:
|
|
211
|
+
print(result)
|
|
212
|
+
|
|
213
|
+
print("=" * 60)
|
|
214
|
+
print("Tests completed!")
|
|
215
|
+
print("=" * 60)
|
|
216
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
from agency_swarm.tools import BaseTool
|
|
3
|
+
from pydantic import Field, field_validator
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from helpers import execute_composio_tool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SendDraft(BaseTool):
|
|
10
|
+
"""
|
|
11
|
+
Sends an existing email draft by its ID.
|
|
12
|
+
|
|
13
|
+
Use this after creating a draft with DraftEmail and getting user approval.
|
|
14
|
+
The draft must have at least one recipient (to, cc, or bcc) to be sent.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
provider: Literal["gmail", "outlook"] = Field(
|
|
18
|
+
...,
|
|
19
|
+
description="Email provider: 'gmail' or 'outlook'"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
draft_id: str = Field(
|
|
23
|
+
...,
|
|
24
|
+
description="The draft ID to send (obtained from DraftEmail tool)"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@field_validator("draft_id")
|
|
28
|
+
@classmethod
|
|
29
|
+
def validate_draft_id(cls, v: str) -> str:
|
|
30
|
+
if "noop" in v.lower():
|
|
31
|
+
raise ValueError("This tool should used to send an existing EMAIL draft. REVIEW YOUR INSTRUCTIONS AND USE THE APPROPRIATE TOOLS TO COMPLETE YOUR TASKS.")
|
|
32
|
+
return v
|
|
33
|
+
|
|
34
|
+
def run(self):
|
|
35
|
+
try:
|
|
36
|
+
if self.provider == "gmail":
|
|
37
|
+
return self._send_gmail_draft(execute_composio_tool)
|
|
38
|
+
else:
|
|
39
|
+
return self._send_outlook_draft(execute_composio_tool)
|
|
40
|
+
|
|
41
|
+
except Exception as e:
|
|
42
|
+
return f"Error sending draft: {str(e)}"
|
|
43
|
+
|
|
44
|
+
def _send_gmail_draft(self, execute_tool) -> str:
|
|
45
|
+
"""Sends a Gmail draft."""
|
|
46
|
+
result = execute_tool(
|
|
47
|
+
tool_name="GMAIL_SEND_DRAFT",
|
|
48
|
+
arguments={"user_id": "me", "draft_id": self.draft_id},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if isinstance(result, dict) and result.get("error"):
|
|
52
|
+
return f"Error sending Gmail draft: {result.get('error')}"
|
|
53
|
+
|
|
54
|
+
data = result.get("data", {})
|
|
55
|
+
|
|
56
|
+
return json.dumps({
|
|
57
|
+
"provider": "gmail",
|
|
58
|
+
"success": True,
|
|
59
|
+
"message": "Email sent successfully",
|
|
60
|
+
"message_id": data.get("id"),
|
|
61
|
+
"thread_id": data.get("threadId"),
|
|
62
|
+
"labels": data.get("labelIds", [])
|
|
63
|
+
}, indent=2)
|
|
64
|
+
|
|
65
|
+
def _send_outlook_draft(self, execute_tool) -> str:
|
|
66
|
+
"""Sends an Outlook draft."""
|
|
67
|
+
result = execute_tool(
|
|
68
|
+
tool_name="OUTLOOK_SEND_DRAFT",
|
|
69
|
+
arguments={"user_id": "me", "message_id": self.draft_id},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if isinstance(result, dict) and result.get("error"):
|
|
73
|
+
return f"Error sending Outlook draft: {result.get('error')}"
|
|
74
|
+
|
|
75
|
+
# Outlook send returns HTTP 202 with no body
|
|
76
|
+
return json.dumps({
|
|
77
|
+
"provider": "outlook",
|
|
78
|
+
"success": True,
|
|
79
|
+
"message": "Email sent successfully",
|
|
80
|
+
"draft_id": self.draft_id
|
|
81
|
+
}, indent=2)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
import sys
|
|
86
|
+
import os
|
|
87
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
88
|
+
|
|
89
|
+
print("=" * 60)
|
|
90
|
+
print("SendDraft Test Suite")
|
|
91
|
+
print("=" * 60)
|
|
92
|
+
print()
|
|
93
|
+
print("To test SendDraft, first create a draft using DraftEmail,")
|
|
94
|
+
print("then use the draft_id to send it.")
|
|
95
|
+
print()
|
|
96
|
+
print("Example:")
|
|
97
|
+
print(" tool = SendDraft(provider='gmail', draft_id='r12345...')")
|
|
98
|
+
print(" result = tool.run()")
|
|
99
|
+
print()
|
|
100
|
+
print("=" * 60)
|
|
101
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from typing import Optional
|
|
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 SendSlackMessage(BaseTool):
|
|
10
|
+
"""
|
|
11
|
+
Sends a message to a Slack channel or DM. Supports threaded replies.
|
|
12
|
+
Use channel ID or name. For replies, provide the parent message timestamp.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
channel: str = Field(
|
|
16
|
+
...,
|
|
17
|
+
description="Channel ID or name (e.g., 'C06NX4Q1ACE', '#general', or '@username')"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
text: str = Field(
|
|
21
|
+
...,
|
|
22
|
+
description="Message text to send. Use Slack formatting."
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
thread_ts: Optional[str] = Field(
|
|
26
|
+
default=None,
|
|
27
|
+
description="Parent message timestamp to reply in thread. Leave empty for new message."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def run(self):
|
|
31
|
+
try:
|
|
32
|
+
# Resolve channel
|
|
33
|
+
channel_id = self._resolve_channel(execute_composio_tool, self.channel)
|
|
34
|
+
if channel_id.startswith("Error"):
|
|
35
|
+
return channel_id
|
|
36
|
+
|
|
37
|
+
# Send message
|
|
38
|
+
args = {
|
|
39
|
+
"channel": channel_id,
|
|
40
|
+
"text": self.text
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if self.thread_ts:
|
|
44
|
+
args["thread_ts"] = self.thread_ts
|
|
45
|
+
|
|
46
|
+
result = execute_composio_tool(
|
|
47
|
+
tool_name="SLACK_SEND_MESSAGE",
|
|
48
|
+
arguments=args,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if result.get("error"):
|
|
52
|
+
return f"Error sending message: {result.get('error')}"
|
|
53
|
+
|
|
54
|
+
data = result.get("data", {})
|
|
55
|
+
msg = data.get("message", {})
|
|
56
|
+
|
|
57
|
+
return json.dumps({
|
|
58
|
+
"success": True,
|
|
59
|
+
"channel_id": channel_id,
|
|
60
|
+
"ts": msg.get("ts"),
|
|
61
|
+
"thread_ts": self.thread_ts,
|
|
62
|
+
"permalink": self._build_permalink(data, channel_id, msg.get("ts"))
|
|
63
|
+
}, indent=2)
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
return f"Error: {str(e)}"
|
|
67
|
+
|
|
68
|
+
def _resolve_channel(self, execute_tool, channel: str) -> str:
|
|
69
|
+
"""Resolves channel/user name to ID."""
|
|
70
|
+
# Already an ID
|
|
71
|
+
if channel.startswith(("C", "D", "G")):
|
|
72
|
+
return channel
|
|
73
|
+
|
|
74
|
+
# Handle @username for DMs
|
|
75
|
+
if channel.startswith("@"):
|
|
76
|
+
return self._find_user_dm(execute_tool, channel[1:])
|
|
77
|
+
|
|
78
|
+
# Remove # prefix and find channel
|
|
79
|
+
name = channel.lstrip("#")
|
|
80
|
+
|
|
81
|
+
result = execute_tool(
|
|
82
|
+
tool_name="SLACK_FIND_CHANNELS",
|
|
83
|
+
arguments={"query": name},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if result.get("error"):
|
|
87
|
+
return f"Error: Could not find channel '{name}'"
|
|
88
|
+
|
|
89
|
+
channels = result.get("data", {}).get("channels", [])
|
|
90
|
+
for ch in channels:
|
|
91
|
+
if ch.get("name") == name:
|
|
92
|
+
return ch.get("id")
|
|
93
|
+
|
|
94
|
+
return f"Error: Channel '{name}' not found"
|
|
95
|
+
|
|
96
|
+
def _find_user_dm(self, execute_tool, username: str) -> str:
|
|
97
|
+
"""Finds DM channel for a user."""
|
|
98
|
+
# Find user
|
|
99
|
+
result = execute_tool(
|
|
100
|
+
tool_name="SLACK_FIND_USERS",
|
|
101
|
+
arguments={"query": username},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if result.get("error"):
|
|
105
|
+
return f"Error: Could not find user '{username}'"
|
|
106
|
+
|
|
107
|
+
members = result.get("data", {}).get("members", [])
|
|
108
|
+
target_user = None
|
|
109
|
+
|
|
110
|
+
for member in members:
|
|
111
|
+
name = member.get("name", "").lower()
|
|
112
|
+
display = member.get("profile", {}).get("display_name", "").lower()
|
|
113
|
+
real = member.get("profile", {}).get("real_name", "").lower()
|
|
114
|
+
|
|
115
|
+
if username.lower() in [name, display, real]:
|
|
116
|
+
target_user = member.get("id")
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
if not target_user:
|
|
120
|
+
return f"Error: User '{username}' not found"
|
|
121
|
+
|
|
122
|
+
# Find existing DM or it will be created when sending
|
|
123
|
+
# For now, return a marker that we need to send to user
|
|
124
|
+
# Slack's SEND_MESSAGE can accept user IDs for DMs
|
|
125
|
+
return target_user
|
|
126
|
+
|
|
127
|
+
def _build_permalink(self, data: dict, channel_id: str, ts: str) -> str:
|
|
128
|
+
"""Builds message permalink if available."""
|
|
129
|
+
# Some responses include permalink directly
|
|
130
|
+
if "permalink" in data:
|
|
131
|
+
return data["permalink"]
|
|
132
|
+
|
|
133
|
+
# Otherwise return empty - would need workspace URL
|
|
134
|
+
return ""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
import sys
|
|
139
|
+
import os
|
|
140
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
141
|
+
|
|
142
|
+
print("SendSlackMessage Test")
|
|
143
|
+
print("-" * 40)
|
|
144
|
+
print("Skipping actual send to avoid spam")
|
|
145
|
+
print("Example usage:")
|
|
146
|
+
print(' SendSlackMessage(channel="#random", text="Hello!")')
|
|
147
|
+
print(' SendSlackMessage(channel="C123", text="Reply", thread_ts="1234.5678")')
|
|
148
|
+
|