@_vrsen/openswarm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/bin/openswarm.js +38 -0
  4. package/config.py +34 -0
  5. package/data_analyst_agent/.cursor/rules/data_analyst.mdc +43 -0
  6. package/data_analyst_agent/__init__.py +3 -0
  7. package/data_analyst_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  8. package/data_analyst_agent/__pycache__/data_analyst_agent.cpython-312.pyc +0 -0
  9. package/data_analyst_agent/data_analyst_agent.py +45 -0
  10. package/data_analyst_agent/instructions.md +173 -0
  11. package/data_analyst_agent/test_files/test_file.csv +21 -0
  12. package/data_analyst_agent/tools/__init__.py +6 -0
  13. package/deep_research/__init__.py +1 -0
  14. package/deep_research/__pycache__/__init__.cpython-312.pyc +0 -0
  15. package/deep_research/__pycache__/deep_research.cpython-312.pyc +0 -0
  16. package/deep_research/deep_research.py +27 -0
  17. package/deep_research/instructions.md +104 -0
  18. package/deep_research/tools/__init__.py +1 -0
  19. package/docs_agent/__init__.py +3 -0
  20. package/docs_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/docs_agent/__pycache__/docs_agent.cpython-312.pyc +0 -0
  22. package/docs_agent/docs_agent.py +61 -0
  23. package/docs_agent/instructions.md +418 -0
  24. package/docs_agent/tools/ConvertDocument.py +323 -0
  25. package/docs_agent/tools/CreateDocument.py +287 -0
  26. package/docs_agent/tools/ListDocuments.py +134 -0
  27. package/docs_agent/tools/ModifyDocument.py +247 -0
  28. package/docs_agent/tools/RestoreDocument.py +79 -0
  29. package/docs_agent/tools/ViewDocument.py +153 -0
  30. package/docs_agent/tools/__init__.py +1 -0
  31. package/docs_agent/tools/__pycache__/ConvertDocument.cpython-312.pyc +0 -0
  32. package/docs_agent/tools/__pycache__/CreateDocument.cpython-312.pyc +0 -0
  33. package/docs_agent/tools/__pycache__/ListDocuments.cpython-312.pyc +0 -0
  34. package/docs_agent/tools/__pycache__/ModifyDocument.cpython-312.pyc +0 -0
  35. package/docs_agent/tools/__pycache__/RestoreDocument.cpython-312.pyc +0 -0
  36. package/docs_agent/tools/__pycache__/ViewDocument.cpython-312.pyc +0 -0
  37. package/docs_agent/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  38. package/docs_agent/tools/utils/__init__.py +1 -0
  39. package/docs_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  40. package/docs_agent/tools/utils/__pycache__/doc_file_utils.cpython-312.pyc +0 -0
  41. package/docs_agent/tools/utils/__pycache__/html_docx_blocks.cpython-312.pyc +0 -0
  42. package/docs_agent/tools/utils/__pycache__/html_docx_constants.cpython-312.pyc +0 -0
  43. package/docs_agent/tools/utils/__pycache__/html_docx_core.cpython-312.pyc +0 -0
  44. package/docs_agent/tools/utils/__pycache__/html_docx_css.cpython-312.pyc +0 -0
  45. package/docs_agent/tools/utils/__pycache__/html_docx_images.cpython-312.pyc +0 -0
  46. package/docs_agent/tools/utils/__pycache__/html_docx_page.cpython-312.pyc +0 -0
  47. package/docs_agent/tools/utils/__pycache__/html_docx_paragraphs.cpython-312.pyc +0 -0
  48. package/docs_agent/tools/utils/__pycache__/html_docx_playwright.cpython-312.pyc +0 -0
  49. package/docs_agent/tools/utils/__pycache__/html_docx_selectors.cpython-312.pyc +0 -0
  50. package/docs_agent/tools/utils/__pycache__/html_docx_shared.cpython-312.pyc +0 -0
  51. package/docs_agent/tools/utils/__pycache__/html_validation.cpython-312.pyc +0 -0
  52. package/docs_agent/tools/utils/doc_file_utils.py +29 -0
  53. package/docs_agent/tools/utils/html_docx_blocks.py +262 -0
  54. package/docs_agent/tools/utils/html_docx_constants.py +78 -0
  55. package/docs_agent/tools/utils/html_docx_core.py +138 -0
  56. package/docs_agent/tools/utils/html_docx_css.py +262 -0
  57. package/docs_agent/tools/utils/html_docx_images.py +293 -0
  58. package/docs_agent/tools/utils/html_docx_page.py +185 -0
  59. package/docs_agent/tools/utils/html_docx_paragraphs.py +342 -0
  60. package/docs_agent/tools/utils/html_docx_playwright.py +184 -0
  61. package/docs_agent/tools/utils/html_docx_selectors.py +196 -0
  62. package/docs_agent/tools/utils/html_docx_shared.py +23 -0
  63. package/docs_agent/tools/utils/html_docx_tables.py +942 -0
  64. package/docs_agent/tools/utils/html_validation.py +102 -0
  65. package/helpers.py +59 -0
  66. package/image_generation_agent/__init__.py +1 -0
  67. package/image_generation_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  68. package/image_generation_agent/__pycache__/image_generation_agent.cpython-312.pyc +0 -0
  69. package/image_generation_agent/image_generation_agent.py +31 -0
  70. package/image_generation_agent/instructions.md +80 -0
  71. package/image_generation_agent/tools/CombineImages.py +211 -0
  72. package/image_generation_agent/tools/EditImages.py +200 -0
  73. package/image_generation_agent/tools/GenerateImages.py +184 -0
  74. package/image_generation_agent/tools/RemoveBackground.py +108 -0
  75. package/image_generation_agent/tools/__init__.py +2 -0
  76. package/image_generation_agent/tools/__pycache__/CombineImages.cpython-312.pyc +0 -0
  77. package/image_generation_agent/tools/__pycache__/EditImages.cpython-312.pyc +0 -0
  78. package/image_generation_agent/tools/__pycache__/GenerateImages.cpython-312.pyc +0 -0
  79. package/image_generation_agent/tools/__pycache__/RemoveBackground.cpython-312.pyc +0 -0
  80. package/image_generation_agent/tools/utils/__init__.py +2 -0
  81. package/image_generation_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  82. package/image_generation_agent/tools/utils/__pycache__/image_io.cpython-312.pyc +0 -0
  83. package/image_generation_agent/tools/utils/image_io.py +308 -0
  84. package/onboard.py +325 -0
  85. package/orchestrator/__init__.py +3 -0
  86. package/orchestrator/__pycache__/__init__.cpython-312.pyc +0 -0
  87. package/orchestrator/__pycache__/orchestrator.cpython-312.pyc +0 -0
  88. package/orchestrator/instructions.md +90 -0
  89. package/orchestrator/orchestrator.py +33 -0
  90. package/package.json +49 -0
  91. package/patches/__init__.py +1 -0
  92. package/patches/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/patches/__pycache__/patch_agency_swarm_dual_comms.cpython-312.pyc +0 -0
  94. package/patches/__pycache__/patch_file_attachment_refs.cpython-312.pyc +0 -0
  95. package/patches/__pycache__/patch_ipython_interpreter_composio.cpython-312.pyc +0 -0
  96. package/patches/dom-to-pptx+1.1.5.patch +133440 -0
  97. package/patches/patch_agency_swarm_dual_comms.py +199 -0
  98. package/patches/patch_file_attachment_refs.py +74 -0
  99. package/patches/patch_ipython_interpreter_composio.py +54 -0
  100. package/pyproject.toml +67 -0
  101. package/run.py +343 -0
  102. package/server.py +26 -0
  103. package/shared_instructions.md +119 -0
  104. package/shared_tools/CopyFile.py +68 -0
  105. package/shared_tools/ExecuteTool.py +184 -0
  106. package/shared_tools/FindTools.py +101 -0
  107. package/shared_tools/ManageConnections.py +43 -0
  108. package/shared_tools/SearchTools.py +44 -0
  109. package/shared_tools/__init__.py +7 -0
  110. package/shared_tools/__pycache__/CopyFile.cpython-312.pyc +0 -0
  111. package/shared_tools/__pycache__/ExecuteTool.cpython-312.pyc +0 -0
  112. package/shared_tools/__pycache__/FindTools.cpython-312.pyc +0 -0
  113. package/shared_tools/__pycache__/ManageConnections.cpython-312.pyc +0 -0
  114. package/shared_tools/__pycache__/SearchTools.cpython-312.pyc +0 -0
  115. package/shared_tools/__pycache__/__init__.cpython-312.pyc +0 -0
  116. package/slides_agent/.cursor/rules/slides-agent-workflow.mdc +9 -0
  117. package/slides_agent/__init__.py +1 -0
  118. package/slides_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  119. package/slides_agent/__pycache__/slides_agent.cpython-312.pyc +0 -0
  120. package/slides_agent/instructions.md +298 -0
  121. package/slides_agent/pptx/SKILL.md +528 -0
  122. package/slides_agent/pptx/html2pptx.md +625 -0
  123. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  124. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  125. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  126. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  127. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  128. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  129. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  130. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  131. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  132. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  133. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  134. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  135. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  136. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  137. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  138. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  139. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  140. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  141. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  142. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  143. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  144. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  145. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  146. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  147. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  148. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  149. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  150. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  151. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  152. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  153. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  154. package/slides_agent/pptx/ooxml/schemas/mce/mc.xsd +75 -0
  155. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  156. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  157. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  158. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  159. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  160. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  161. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  162. package/slides_agent/pptx/ooxml/scripts/pack.py +169 -0
  163. package/slides_agent/pptx/ooxml/scripts/unpack.py +29 -0
  164. package/slides_agent/pptx/ooxml/scripts/validate.py +69 -0
  165. package/slides_agent/pptx/ooxml/scripts/validation/__init__.py +15 -0
  166. package/slides_agent/pptx/ooxml/scripts/validation/base.py +951 -0
  167. package/slides_agent/pptx/ooxml/scripts/validation/docx.py +274 -0
  168. package/slides_agent/pptx/ooxml/scripts/validation/pptx.py +315 -0
  169. package/slides_agent/pptx/ooxml/scripts/validation/redlining.py +279 -0
  170. package/slides_agent/pptx/ooxml.md +427 -0
  171. package/slides_agent/pptx/scripts/html2pptx.js +1092 -0
  172. package/slides_agent/pptx/scripts/inventory.py +1020 -0
  173. package/slides_agent/pptx/scripts/rearrange.py +231 -0
  174. package/slides_agent/pptx/scripts/replace.py +385 -0
  175. package/slides_agent/pptx/scripts/thumbnail.py +451 -0
  176. package/slides_agent/slides_agent.py +109 -0
  177. package/slides_agent/test_deck/_theme.css +92 -0
  178. package/slides_agent/test_deck/assets/placeholder.svg +11 -0
  179. package/slides_agent/test_deck/slide_01_title.html +10 -0
  180. package/slides_agent/test_deck/slide_02_image_split.html +23 -0
  181. package/slides_agent/test_deck/slide_03_kpi.html +21 -0
  182. package/slides_agent/tools/ApplyPptxTextReplacements.py +91 -0
  183. package/slides_agent/tools/BuildPptxFromHtmlSlides.py +221 -0
  184. package/slides_agent/tools/CheckSlide.py +218 -0
  185. package/slides_agent/tools/CheckSlideCanvasOverflow.py +221 -0
  186. package/slides_agent/tools/CreateImageMontage.py +261 -0
  187. package/slides_agent/tools/CreatePptxThumbnailGrid.py +168 -0
  188. package/slides_agent/tools/DeleteSlide.py +78 -0
  189. package/slides_agent/tools/DownloadImage.py +79 -0
  190. package/slides_agent/tools/EnsureRasterImage.py +157 -0
  191. package/slides_agent/tools/ExtractPptxTextInventory.py +104 -0
  192. package/slides_agent/tools/GenerateImage.py +189 -0
  193. package/slides_agent/tools/ImageSearch.py +127 -0
  194. package/slides_agent/tools/InsertNewSlides.py +393 -0
  195. package/slides_agent/tools/ManageTheme.py +112 -0
  196. package/slides_agent/tools/ModifySlide.py +563 -0
  197. package/slides_agent/tools/ReadSlide.py +26 -0
  198. package/slides_agent/tools/RearrangePptxSlidesFromTemplate.py +114 -0
  199. package/slides_agent/tools/RestoreSnapshot.py +89 -0
  200. package/slides_agent/tools/SlideScreenshot.py +66 -0
  201. package/slides_agent/tools/__init__.py +54 -0
  202. package/slides_agent/tools/__pycache__/ApplyPptxTextReplacements.cpython-312.pyc +0 -0
  203. package/slides_agent/tools/__pycache__/BuildPptxFromHtmlSlides.cpython-312.pyc +0 -0
  204. package/slides_agent/tools/__pycache__/CheckSlide.cpython-312.pyc +0 -0
  205. package/slides_agent/tools/__pycache__/CheckSlideCanvasOverflow.cpython-312.pyc +0 -0
  206. package/slides_agent/tools/__pycache__/CreateImageMontage.cpython-312.pyc +0 -0
  207. package/slides_agent/tools/__pycache__/CreatePptxThumbnailGrid.cpython-312.pyc +0 -0
  208. package/slides_agent/tools/__pycache__/DeleteSlide.cpython-312.pyc +0 -0
  209. package/slides_agent/tools/__pycache__/DownloadImage.cpython-312.pyc +0 -0
  210. package/slides_agent/tools/__pycache__/EnsureRasterImage.cpython-312.pyc +0 -0
  211. package/slides_agent/tools/__pycache__/ExtractPptxTextInventory.cpython-312.pyc +0 -0
  212. package/slides_agent/tools/__pycache__/GenerateImage.cpython-312.pyc +0 -0
  213. package/slides_agent/tools/__pycache__/ImageSearch.cpython-312.pyc +0 -0
  214. package/slides_agent/tools/__pycache__/InsertNewSlides.cpython-312.pyc +0 -0
  215. package/slides_agent/tools/__pycache__/ManageTheme.cpython-312.pyc +0 -0
  216. package/slides_agent/tools/__pycache__/ModifySlide.cpython-312.pyc +0 -0
  217. package/slides_agent/tools/__pycache__/ReadSlide.cpython-312.pyc +0 -0
  218. package/slides_agent/tools/__pycache__/RearrangePptxSlidesFromTemplate.cpython-312.pyc +0 -0
  219. package/slides_agent/tools/__pycache__/RestoreSnapshot.cpython-312.pyc +0 -0
  220. package/slides_agent/tools/__pycache__/SlideScreenshot.cpython-312.pyc +0 -0
  221. package/slides_agent/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  222. package/slides_agent/tools/__pycache__/slide_file_utils.cpython-312.pyc +0 -0
  223. package/slides_agent/tools/__pycache__/slide_html_utils.cpython-312.pyc +0 -0
  224. package/slides_agent/tools/__pycache__/template_registry.cpython-312.pyc +0 -0
  225. package/slides_agent/tools/deck_utils.py +31 -0
  226. package/slides_agent/tools/html2pptx_runner.js +1183 -0
  227. package/slides_agent/tools/html_writer_instructions.md +149 -0
  228. package/slides_agent/tools/slide_file_utils.py +108 -0
  229. package/slides_agent/tools/slide_html_utils.py +354 -0
  230. package/slides_agent/tools/template_registry.py +55 -0
  231. package/swarm.py +82 -0
  232. package/video_generation_agent/__init__.py +1 -0
  233. package/video_generation_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  234. package/video_generation_agent/__pycache__/video_generation_agent.cpython-312.pyc +0 -0
  235. package/video_generation_agent/instructions.md +178 -0
  236. package/video_generation_agent/tools/AddSubtitles.py +425 -0
  237. package/video_generation_agent/tools/CombineImages.py +166 -0
  238. package/video_generation_agent/tools/CombineVideos.py +113 -0
  239. package/video_generation_agent/tools/EditAudio.py +297 -0
  240. package/video_generation_agent/tools/EditImage.py +144 -0
  241. package/video_generation_agent/tools/EditVideoContent.py +369 -0
  242. package/video_generation_agent/tools/GenerateImage.py +133 -0
  243. package/video_generation_agent/tools/GenerateVideo.py +556 -0
  244. package/video_generation_agent/tools/TrimVideo.py +233 -0
  245. package/video_generation_agent/tools/__init__.py +1 -0
  246. package/video_generation_agent/tools/__pycache__/AddSubtitles.cpython-312.pyc +0 -0
  247. package/video_generation_agent/tools/__pycache__/CombineImages.cpython-312.pyc +0 -0
  248. package/video_generation_agent/tools/__pycache__/CombineVideos.cpython-312.pyc +0 -0
  249. package/video_generation_agent/tools/__pycache__/EditAudio.cpython-312.pyc +0 -0
  250. package/video_generation_agent/tools/__pycache__/EditImage.cpython-312.pyc +0 -0
  251. package/video_generation_agent/tools/__pycache__/EditVideoContent.cpython-312.pyc +0 -0
  252. package/video_generation_agent/tools/__pycache__/GenerateImage.cpython-312.pyc +0 -0
  253. package/video_generation_agent/tools/__pycache__/GenerateVideo.cpython-312.pyc +0 -0
  254. package/video_generation_agent/tools/__pycache__/TrimVideo.cpython-312.pyc +0 -0
  255. package/video_generation_agent/tools/utils/__init__.py +1 -0
  256. package/video_generation_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  257. package/video_generation_agent/tools/utils/__pycache__/image_utils.cpython-312.pyc +0 -0
  258. package/video_generation_agent/tools/utils/__pycache__/video_utils.cpython-312.pyc +0 -0
  259. package/video_generation_agent/tools/utils/image_utils.py +174 -0
  260. package/video_generation_agent/tools/utils/video_utils.py +522 -0
  261. package/video_generation_agent/video_generation_agent.py +26 -0
  262. package/virtual_assistant/__init__.py +1 -0
  263. package/virtual_assistant/__pycache__/__init__.cpython-312.pyc +0 -0
  264. package/virtual_assistant/__pycache__/virtual_assistant.cpython-312.pyc +0 -0
  265. package/virtual_assistant/instructions.md +206 -0
  266. package/virtual_assistant/tools/AddLabelToEmail.py +154 -0
  267. package/virtual_assistant/tools/CheckEventsForDate.py +218 -0
  268. package/virtual_assistant/tools/CheckUnreadSlackMessages.py +216 -0
  269. package/virtual_assistant/tools/CreateCalendarEvent.py +261 -0
  270. package/virtual_assistant/tools/DeleteCalendarEvent.py +137 -0
  271. package/virtual_assistant/tools/DeleteDraft.py +95 -0
  272. package/virtual_assistant/tools/DraftEmail.py +239 -0
  273. package/virtual_assistant/tools/EditFile.py +113 -0
  274. package/virtual_assistant/tools/FindEmails.py +330 -0
  275. package/virtual_assistant/tools/GetCurrentTime.py +69 -0
  276. package/virtual_assistant/tools/GetSlackUserInfo.py +117 -0
  277. package/virtual_assistant/tools/ListDirectory.py +113 -0
  278. package/virtual_assistant/tools/ListSkills.py +94 -0
  279. package/virtual_assistant/tools/ManageLabels.py +295 -0
  280. package/virtual_assistant/tools/ProductSearch.py +254 -0
  281. package/virtual_assistant/tools/ReadEmail.py +251 -0
  282. package/virtual_assistant/tools/ReadFile.py +108 -0
  283. package/virtual_assistant/tools/ReadSlackMessages.py +191 -0
  284. package/virtual_assistant/tools/RemoveLabelFromEmail.py +137 -0
  285. package/virtual_assistant/tools/RescheduleCalendarEvent.py +227 -0
  286. package/virtual_assistant/tools/ScholarSearch.py +216 -0
  287. package/virtual_assistant/tools/SendDraft.py +101 -0
  288. package/virtual_assistant/tools/SendSlackMessage.py +148 -0
  289. package/virtual_assistant/tools/WriteFile.py +95 -0
  290. package/virtual_assistant/tools/__init__.py +1 -0
  291. package/virtual_assistant/tools/__pycache__/AddLabelToEmail.cpython-312.pyc +0 -0
  292. package/virtual_assistant/tools/__pycache__/CheckEventsForDate.cpython-312.pyc +0 -0
  293. package/virtual_assistant/tools/__pycache__/CheckUnreadSlackMessages.cpython-312.pyc +0 -0
  294. package/virtual_assistant/tools/__pycache__/CreateCalendarEvent.cpython-312.pyc +0 -0
  295. package/virtual_assistant/tools/__pycache__/DeleteCalendarEvent.cpython-312.pyc +0 -0
  296. package/virtual_assistant/tools/__pycache__/DeleteDraft.cpython-312.pyc +0 -0
  297. package/virtual_assistant/tools/__pycache__/DraftEmail.cpython-312.pyc +0 -0
  298. package/virtual_assistant/tools/__pycache__/EditFile.cpython-312.pyc +0 -0
  299. package/virtual_assistant/tools/__pycache__/FindEmails.cpython-312.pyc +0 -0
  300. package/virtual_assistant/tools/__pycache__/GetCurrentTime.cpython-312.pyc +0 -0
  301. package/virtual_assistant/tools/__pycache__/GetSlackUserInfo.cpython-312.pyc +0 -0
  302. package/virtual_assistant/tools/__pycache__/ListDirectory.cpython-312.pyc +0 -0
  303. package/virtual_assistant/tools/__pycache__/ListSkills.cpython-312.pyc +0 -0
  304. package/virtual_assistant/tools/__pycache__/ManageLabels.cpython-312.pyc +0 -0
  305. package/virtual_assistant/tools/__pycache__/ProductSearch.cpython-312.pyc +0 -0
  306. package/virtual_assistant/tools/__pycache__/ReadEmail.cpython-312.pyc +0 -0
  307. package/virtual_assistant/tools/__pycache__/ReadFile.cpython-312.pyc +0 -0
  308. package/virtual_assistant/tools/__pycache__/ReadSlackMessages.cpython-312.pyc +0 -0
  309. package/virtual_assistant/tools/__pycache__/RemoveLabelFromEmail.cpython-312.pyc +0 -0
  310. package/virtual_assistant/tools/__pycache__/RescheduleCalendarEvent.cpython-312.pyc +0 -0
  311. package/virtual_assistant/tools/__pycache__/ScholarSearch.cpython-312.pyc +0 -0
  312. package/virtual_assistant/tools/__pycache__/SendDraft.cpython-312.pyc +0 -0
  313. package/virtual_assistant/tools/__pycache__/SendSlackMessage.cpython-312.pyc +0 -0
  314. package/virtual_assistant/tools/__pycache__/WriteFile.cpython-312.pyc +0 -0
  315. package/virtual_assistant/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  316. package/virtual_assistant/virtual_assistant.py +52 -0
@@ -0,0 +1,78 @@
1
+ """Delete HTML slide files from a presentation project."""
2
+
3
+ from .slide_file_utils import get_project_dir, list_slide_files
4
+
5
+ from agency_swarm.tools import BaseTool
6
+ from pydantic import Field
7
+
8
+
9
+ class DeleteSlide(BaseTool):
10
+ """
11
+ Delete an HTML slide file from a presentation project.
12
+
13
+ Use this tool to remove slides that are no longer needed.
14
+ """
15
+
16
+ project_name: str = Field(
17
+ ...,
18
+ description="Name of the presentation project"
19
+ )
20
+ slide_name: str | None = Field(
21
+ default=None,
22
+ description="Name of the slide file to delete (e.g., 'slide_01' - .html extension added automatically)"
23
+ )
24
+ slide_indexes: list[int] | None = Field(
25
+ default=None,
26
+ description="1-based slide indexes to delete (uses project slide order)",
27
+ )
28
+ file_prefix: str = Field(
29
+ default="slide",
30
+ description="Slide filename prefix when deleting by index",
31
+ )
32
+
33
+ def run(self):
34
+ """Delete the specified slide file."""
35
+ project_dir = get_project_dir(self.project_name)
36
+
37
+ if not project_dir.exists():
38
+ return f"❌ Project '{self.project_name}' does not exist at {project_dir}"
39
+
40
+ if self.slide_indexes:
41
+ slides = list_slide_files(project_dir, self.file_prefix)
42
+ missing = [idx for idx in self.slide_indexes if idx < 1 or idx > len(slides)]
43
+ if missing:
44
+ return f"❌ Slide indexes out of range: {missing}"
45
+ deleted = []
46
+ for idx in sorted(self.slide_indexes, reverse=True):
47
+ slide_path = slides[idx - 1].path
48
+ if slide_path.exists():
49
+ slide_path.unlink()
50
+ deleted.append(slide_path)
51
+ if not deleted:
52
+ return "❌ No slides deleted."
53
+ return "✅ Deleted slides:\n" + "\n".join(f"- {path}" for path in deleted)
54
+
55
+ if not self.slide_name:
56
+ return "Error: Provide slide_name or slide_indexes to delete slides."
57
+
58
+ slide_name = self.slide_name if self.slide_name.endswith('.html') else f"{self.slide_name}.html"
59
+ slide_path = project_dir / slide_name
60
+
61
+ if not slide_path.exists():
62
+ return f"❌ Slide '{slide_name}' does not exist in project '{self.project_name}'"
63
+
64
+ try:
65
+ slide_path.unlink()
66
+ return f"✅ Successfully deleted slide: {slide_path}"
67
+
68
+ except Exception as e:
69
+ return f"Error deleting slide: {e}"
70
+
71
+
72
+ if __name__ == "__main__":
73
+ # Test (will fail if file doesn't exist, which is expected)
74
+ tool = DeleteSlide(
75
+ project_name="test_project",
76
+ slide_name="slide_01"
77
+ )
78
+ print(tool.run())
@@ -0,0 +1,79 @@
1
+ """Download an image into a project's assets folder."""
2
+
3
+ from pathlib import Path
4
+ from urllib.parse import urlparse
5
+ from urllib.request import Request, urlopen
6
+
7
+ from agency_swarm.tools import BaseTool
8
+ from pydantic import Field
9
+
10
+ from .slide_file_utils import get_project_dir
11
+
12
+
13
+ class DownloadImage(BaseTool):
14
+ """
15
+ Download an image from a URL into the project's assets folder.
16
+ """
17
+
18
+ project_name: str = Field(..., description="Name of the presentation project")
19
+ url: str = Field(..., description="Image URL to download")
20
+ image_name: str = Field(..., description="Desired filename (with or without extension)")
21
+
22
+ def run(self) -> str:
23
+ project_dir = get_project_dir(self.project_name)
24
+ assets_dir = project_dir / "assets"
25
+ assets_dir.mkdir(parents=True, exist_ok=True)
26
+
27
+ parsed = urlparse(self.url)
28
+ if parsed.scheme not in ("http", "https"):
29
+ return "Error: URL must start with http or https."
30
+
31
+ file_name = self._ensure_extension(self.image_name, self.url)
32
+ output_path = assets_dir / file_name
33
+
34
+ try:
35
+ request = Request(self.url, headers={"User-Agent": "slides_agent/1.0"})
36
+ with urlopen(request, timeout=30) as response:
37
+ content = response.read()
38
+ # Reject HTML or non-image content (e.g. error pages)
39
+ if content.lstrip()[:100].lower().startswith((b"<!doctype", b"<html", b"< ")):
40
+ return "Error: URL did not return an image (got HTML). Use a direct image URL or try another source."
41
+ output_path.write_bytes(content)
42
+ # Verify we can read it as an image (SVGs are validated separately)
43
+ if output_path.suffix.lower() == ".svg":
44
+ snippet = content.lstrip()[:200].lower()
45
+ if b"<svg" not in snippet and b"<?xml" not in snippet:
46
+ output_path.unlink(missing_ok=True)
47
+ return "Error: Downloaded file is not a valid SVG. Use a direct image URL or try another source."
48
+ else:
49
+ try:
50
+ from PIL import Image
51
+ with Image.open(output_path) as img:
52
+ img.verify()
53
+ except Exception:
54
+ output_path.unlink(missing_ok=True)
55
+ return "Error: Downloaded file is not a valid image. Use a direct image URL (e.g. .jpg, .png) or try another source."
56
+ except Exception as exc:
57
+ return f"Error downloading image: {exc}"
58
+
59
+ return f"Downloaded image to: {output_path}"
60
+
61
+ def _ensure_extension(self, image_name: str, url: str) -> str:
62
+ if Path(image_name).suffix:
63
+ return image_name
64
+
65
+ url_path = urlparse(url).path
66
+ url_ext = Path(url_path).suffix
67
+ if url_ext:
68
+ return image_name + url_ext
69
+
70
+ return image_name + ".png"
71
+
72
+
73
+ if __name__ == "__main__":
74
+ tool = DownloadImage(
75
+ project_name="openclaw_presentation_v3",
76
+ url="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/convex.svg",
77
+ image_name="sponsor_convex.svg"
78
+ )
79
+ print(tool.run())
@@ -0,0 +1,157 @@
1
+ """Convert vector/container image formats to raster PNG."""
2
+
3
+ import gzip
4
+ import shutil
5
+ import subprocess
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from agency_swarm.tools import BaseTool
10
+ from pydantic import Field
11
+
12
+ # Supported file extensions
13
+ RASTER_EXTS = {".png", ".jpg", ".jpeg", ".bmp", ".gif", ".tif", ".tiff", ".webp"}
14
+ CONVERTIBLE_EXTS = {
15
+ ".emf", ".wmf", ".emz", ".wmz", # Windows metafiles
16
+ ".svg", ".svgz", # SVG
17
+ ".wdp", ".jxr", # JPEG XR
18
+ ".heic", ".heif", # HEIF
19
+ ".pdf", ".eps", ".ps", # Page description formats
20
+ }
21
+ SUPPORTED_EXTS = RASTER_EXTS | CONVERTIBLE_EXTS
22
+
23
+
24
+ class EnsureRasterImage(BaseTool):
25
+ """
26
+ Convert vector or container image formats to raster PNG.
27
+
28
+ Supports conversion of:
29
+ - EMF/WMF/EMZ/WMZ (Windows metafiles) via Inkscape
30
+ - SVG/SVGZ via Inkscape
31
+ - WDP/JXR (JPEG XR) via JxrDecApp + ImageMagick
32
+ - HEIC/HEIF via heif-convert
33
+ - PDF/EPS/PS (first page) via Ghostscript
34
+
35
+ Already-raster formats (PNG, JPG, etc.) are returned as-is.
36
+
37
+ This is useful for preparing images extracted from PowerPoint files
38
+ for preview or re-embedding, as PPTX files may contain vector formats
39
+ that need rasterization.
40
+
41
+ Required external tools (depending on input format):
42
+ - Inkscape: SVG/EMF/WMF rasterization
43
+ - ImageMagick: format bridging
44
+ - Ghostscript: PDF/EPS/PS rasterization
45
+ - libheif-examples: HEIC/HEIF conversion
46
+ - jxr-tools: JPEG XR conversion
47
+ """
48
+
49
+ input_path: str = Field(
50
+ ...,
51
+ description="Path to the input image file",
52
+ )
53
+ output_dir: Optional[str] = Field(
54
+ default=None,
55
+ description="Directory for output PNG (defaults to same directory as input)",
56
+ )
57
+ dpi: Optional[int] = Field(
58
+ default=None,
59
+ description="Optional rasterization DPI for vector formats (e.g., 192 for crisp icons)",
60
+ )
61
+
62
+ def run(self) -> str:
63
+ """Convert image to PNG if needed and return the output path."""
64
+ input_path = Path(self.input_path)
65
+
66
+ # Validate input
67
+ if not input_path.exists():
68
+ return f"Error: Input file not found: {self.input_path}"
69
+
70
+ ext_lower = input_path.suffix.lower()
71
+ if ext_lower not in SUPPORTED_EXTS:
72
+ return f"Error: Unsupported format '{ext_lower}'. Supported: {', '.join(sorted(SUPPORTED_EXTS))}"
73
+
74
+ # Determine output directory and path
75
+ out_dir = Path(self.output_dir) if self.output_dir else input_path.parent
76
+ out_dir.mkdir(parents=True, exist_ok=True)
77
+ out_path = out_dir / (input_path.stem + ".png")
78
+
79
+ # Already raster - return as-is
80
+ if ext_lower in RASTER_EXTS:
81
+ return f"Already raster format: {input_path}"
82
+
83
+ try:
84
+ result_path = self._convert(input_path, out_path, ext_lower, self.dpi)
85
+ return f"Converted to: {result_path}"
86
+ except Exception as e:
87
+ return f"Error converting {input_path}: {e}"
88
+
89
+ def _convert(self, input_path: Path, out_path: Path, ext: str, dpi: Optional[int]) -> str:
90
+ """Convert the file and return the output path."""
91
+ out_dir = out_path.parent
92
+ dpi_arg = [f"--export-dpi={dpi}"] if dpi else []
93
+
94
+ # EMF/WMF via Inkscape
95
+ if ext in (".emf", ".wmf"):
96
+ self._run_cmd(["inkscape", str(input_path), "-o", str(out_path), *dpi_arg])
97
+ return str(out_path)
98
+
99
+ # EMZ/WMZ - decompress then convert
100
+ if ext in (".emz", ".wmz"):
101
+ decompressed = out_dir / (input_path.stem + (".emf" if ext == ".emz" else ".wmf"))
102
+ with gzip.open(input_path, "rb") as zin, open(decompressed, "wb") as zout:
103
+ zout.write(zin.read())
104
+ self._run_cmd(["inkscape", str(decompressed), "-o", str(out_path), *dpi_arg])
105
+ decompressed.unlink() # Clean up
106
+ return str(out_path)
107
+
108
+ # SVG/SVGZ via Inkscape
109
+ if ext in (".svg", ".svgz"):
110
+ self._run_cmd(["inkscape", str(input_path), "-o", str(out_path), *dpi_arg])
111
+ return str(out_path)
112
+
113
+ # JPEG XR via JxrDecApp + ImageMagick
114
+ if ext in (".wdp", ".jxr"):
115
+ tmp_tiff = out_dir / (input_path.stem + ".tiff")
116
+ self._run_cmd(["JxrDecApp", "-i", str(input_path), "-o", str(tmp_tiff)])
117
+ self._imagemagick_convert(str(tmp_tiff), str(out_path))
118
+ tmp_tiff.unlink() # Clean up
119
+ return str(out_path)
120
+
121
+ # HEIC/HEIF via heif-convert
122
+ if ext in (".heic", ".heif"):
123
+ heif_convert = shutil.which("heif-convert") or "heif-convert"
124
+ self._run_cmd([heif_convert, str(input_path), str(out_path)])
125
+ return str(out_path)
126
+
127
+ # PDF/EPS/PS via Ghostscript (first page only)
128
+ if ext in (".pdf", ".eps", ".ps"):
129
+ gs = shutil.which("gs") or "gs"
130
+ dpi_value = str(dpi or 200)
131
+ self._run_cmd([
132
+ gs, "-dSAFER", "-dBATCH", "-dNOPAUSE",
133
+ "-sDEVICE=pngalpha",
134
+ "-dFirstPage=1", "-dLastPage=1",
135
+ f"-r{dpi_value}",
136
+ "-o", str(out_path),
137
+ str(input_path),
138
+ ])
139
+ return str(out_path)
140
+
141
+ raise ValueError(f"No conversion handler for {ext}")
142
+
143
+ def _run_cmd(self, cmd: list) -> None:
144
+ """Run a command and raise on failure."""
145
+ result = subprocess.run(cmd, capture_output=True, text=True)
146
+ if result.returncode != 0:
147
+ raise RuntimeError(f"Command failed: {' '.join(cmd)}\n{result.stderr}")
148
+
149
+ def _imagemagick_convert(self, src: str, dst: str) -> None:
150
+ """Convert using ImageMagick."""
151
+ binary = shutil.which("magick") or "convert"
152
+ self._run_cmd([binary, src, dst])
153
+
154
+
155
+ if __name__ == "__main__":
156
+ print("EnsureRasterImage tool definition is valid.")
157
+ print(f"Supported formats: {', '.join(sorted(SUPPORTED_EXTS))}")
@@ -0,0 +1,104 @@
1
+ """Extract structured text inventory from a PowerPoint presentation."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from agency_swarm.tools import BaseTool
7
+ from pydantic import Field
8
+
9
+ # Add pptx/scripts to path for inventory module
10
+ PPTX_SCRIPTS_DIR = Path(__file__).parent.parent / "pptx" / "scripts"
11
+ sys.path.insert(0, str(PPTX_SCRIPTS_DIR))
12
+
13
+
14
+ class ExtractPptxTextInventory(BaseTool):
15
+ """
16
+ Extract structured text inventory from a PowerPoint presentation.
17
+
18
+ Returns a JSON file containing all text shapes organized by slide, with:
19
+ - Position and dimensions (in inches)
20
+ - Placeholder types (TITLE, BODY, SUBTITLE, etc.)
21
+ - Paragraph formatting (bullets, alignment, fonts, colors, spacing)
22
+ - Overflow and overlap issue detection
23
+
24
+ Use this tool to understand the structure of a presentation before
25
+ making text replacements, or to detect formatting issues.
26
+ """
27
+
28
+ input_pptx: str = Field(
29
+ ...,
30
+ description="Path to the input PowerPoint file (.pptx)",
31
+ )
32
+ output_json: str = Field(
33
+ ...,
34
+ description="Path where the inventory JSON will be saved",
35
+ )
36
+ issues_only: bool = Field(
37
+ default=False,
38
+ description="If True, only include shapes with overflow or overlap issues",
39
+ )
40
+
41
+ def run(self) -> str:
42
+ """Extract text inventory and save to JSON."""
43
+ import sys
44
+ from pathlib import Path as PathLib
45
+
46
+ # Add pptx/scripts to path for inventory import
47
+ scripts_dir = PathLib(__file__).parent.parent / "pptx" / "scripts"
48
+ if str(scripts_dir) not in sys.path:
49
+ sys.path.insert(0, str(scripts_dir))
50
+
51
+ from inventory import extract_text_inventory, save_inventory # type: ignore
52
+
53
+ input_path = Path(self.input_pptx)
54
+ output_path = Path(self.output_json)
55
+
56
+ # Validate input
57
+ if not input_path.exists():
58
+ return f"Error: Input file not found: {self.input_pptx}"
59
+ if input_path.suffix.lower() != ".pptx":
60
+ return f"Error: Input must be a PowerPoint file (.pptx), got: {input_path.suffix}"
61
+
62
+ # Ensure output directory exists
63
+ output_path.parent.mkdir(parents=True, exist_ok=True)
64
+
65
+ # Extract inventory
66
+ inventory = extract_text_inventory(input_path, issues_only=self.issues_only)
67
+
68
+ # Save to JSON
69
+ save_inventory(inventory, output_path)
70
+
71
+ # Generate summary
72
+ total_slides = len(inventory)
73
+ total_shapes = sum(len(shapes) for shapes in inventory.values())
74
+
75
+ if self.issues_only:
76
+ if total_shapes > 0:
77
+ return (
78
+ f"Inventory saved to: {output_path}\n"
79
+ f"Found {total_shapes} text elements with issues across {total_slides} slides"
80
+ )
81
+ else:
82
+ return f"Inventory saved to: {output_path}\nNo issues discovered"
83
+ else:
84
+ return (
85
+ f"Inventory saved to: {output_path}\n"
86
+ f"Found {total_shapes} text elements across {total_slides} slides"
87
+ )
88
+
89
+
90
+ if __name__ == "__main__":
91
+ # Test with a sample file if available
92
+ import tempfile
93
+
94
+ test_pptx = Path(__file__).parent.parent / "files" / "test.pptx"
95
+ if test_pptx.exists():
96
+ with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
97
+ tool = ExtractPptxTextInventory(
98
+ input_pptx=str(test_pptx),
99
+ output_json=f.name,
100
+ )
101
+ print(tool.run())
102
+ else:
103
+ print(f"Test file not found: {test_pptx}")
104
+ print("Tool definition is valid.")
@@ -0,0 +1,189 @@
1
+ """Generate images using AI models for diagrams and concept art."""
2
+
3
+ from typing import Literal, Optional
4
+ import asyncio
5
+ import inspect
6
+
7
+ import os
8
+ from agency_swarm.tools import BaseTool, LoadFileAttachment
9
+ from pydantic import Field
10
+
11
+
12
+ class GenerateImage(BaseTool):
13
+ """
14
+ Generate images using Google Gemini models.
15
+
16
+ Supports two modes:
17
+ - Complex Diagrams (Flowcharts, Pyramids, Org Charts): Uses nano-banana-pro (gemini-3-pro-image-preview) optimized for text rendering
18
+ - Concept Art (Illustrations, Atmosphere): Uses nano-banana (gemini-2.5-flash-image) for faster generation
19
+
20
+ The generated image is saved to the specified output path or to the project's assets folder.
21
+ """
22
+
23
+ prompt: str = Field(
24
+ ...,
25
+ description="Detailed description of the image to generate. Be specific about layout, colors, style, and any text that should appear."
26
+ )
27
+ image_type: Literal["diagram", "concept_art"] = Field(
28
+ ...,
29
+ description="Type of image: 'diagram' for flowcharts/org charts (uses nano-banana-pro), 'concept_art' for illustrations"
30
+ )
31
+ project_name: str = Field(
32
+ ...,
33
+ description="Name of the presentation project (e.g. 'my_presentation'). The image is saved to that project's assets folder."
34
+ )
35
+ asset_name: str = Field(
36
+ ...,
37
+ description="Filename for the generated image including extension (e.g. 'hero.png'). Saved under the project's assets/ folder."
38
+ )
39
+ width: int = Field(
40
+ default=1024,
41
+ description="Image width in pixels (default 1024)"
42
+ )
43
+ height: int = Field(
44
+ default=1024,
45
+ description="Image height in pixels (default 1024)"
46
+ )
47
+ style: Optional[str] = Field(
48
+ default=None,
49
+ description="Optional style modifier (e.g., 'minimalist', 'professional', 'hand-drawn', 'technical')"
50
+ )
51
+
52
+ def run(self) -> str:
53
+ """Generate image and save to the project's assets folder."""
54
+ from .slide_file_utils import get_project_dir
55
+ try:
56
+ full_prompt = self.prompt
57
+ if self.style:
58
+ full_prompt = f"{self.prompt} Style: {self.style}"
59
+
60
+ if self.image_type == "diagram":
61
+ model = "gemini-3-pro-image-preview"
62
+ full_prompt = f"Technical diagram: {full_prompt}. Clear labels, professional layout, high contrast."
63
+ else:
64
+ model = "gemini-2.5-flash-image"
65
+ full_prompt = f"High-quality illustration: {full_prompt}"
66
+
67
+ image_data = self._generate_with_gemini(full_prompt, model)
68
+
69
+ assets_dir = get_project_dir(self.project_name) / "assets"
70
+ assets_dir.mkdir(parents=True, exist_ok=True)
71
+ output = assets_dir / self.asset_name
72
+
73
+ # Compress image to reduce token usage
74
+ from PIL import Image
75
+ import io
76
+
77
+ # Load image from bytes
78
+ img = Image.open(io.BytesIO(image_data))
79
+
80
+ # Resize to 75% if larger than 1024px on any dimension
81
+ if img.width > 1024 or img.height > 1024:
82
+ new_size = (int(img.width * 0.75), int(img.height * 0.75))
83
+ img = img.resize(new_size, Image.Resampling.LANCZOS)
84
+
85
+ # Save with JPEG quality 80 for good balance
86
+ img.save(output, 'JPEG', quality=80, optimize=True)
87
+
88
+ # Return image as file attachment
89
+ attachment = LoadFileAttachment(path=str(output))
90
+ result = self._run_attachment(attachment)
91
+ return f"Image saved to ./assets/{self.asset_name}\n\n{result}"
92
+
93
+ except Exception as e:
94
+ return f"Error generating image: {e}"
95
+
96
+ def _run_attachment(self, attachment):
97
+ """Run the attachment tool, handling async if needed."""
98
+ result = attachment.run()
99
+ if not inspect.isawaitable(result):
100
+ return result
101
+
102
+ try:
103
+ asyncio.get_running_loop()
104
+ except RuntimeError:
105
+ return asyncio.run(result)
106
+
107
+ new_loop = asyncio.new_event_loop()
108
+ try:
109
+ return new_loop.run_until_complete(result)
110
+ finally:
111
+ new_loop.close()
112
+
113
+ def _generate_with_gemini(self, prompt: str, model: str) -> bytes:
114
+ """Generate image using Google Gemini API (nano-banana or nano-banana-pro)."""
115
+ try:
116
+ from google import genai
117
+ from google.genai.types import GenerateContentConfig, ImageConfig
118
+
119
+ api_key = os.getenv("GOOGLE_API_KEY")
120
+ if not api_key:
121
+ raise ValueError("GOOGLE_API_KEY is not set. Add it to your .env to use image generation.")
122
+
123
+ client = genai.Client(api_key=api_key)
124
+
125
+ # Determine aspect ratio based on width/height
126
+ aspect_ratio = "1:1" # default square
127
+ if self.width != self.height:
128
+ # Calculate approximate aspect ratio
129
+ ratio = self.width / self.height
130
+ if abs(ratio - 16/9) < 0.1:
131
+ aspect_ratio = "16:9"
132
+ elif abs(ratio - 9/16) < 0.1:
133
+ aspect_ratio = "9:16"
134
+ elif abs(ratio - 4/3) < 0.1:
135
+ aspect_ratio = "4:3"
136
+ elif abs(ratio - 3/4) < 0.1:
137
+ aspect_ratio = "3:4"
138
+ elif abs(ratio - 3/2) < 0.1:
139
+ aspect_ratio = "3:2"
140
+ elif abs(ratio - 2/3) < 0.1:
141
+ aspect_ratio = "2:3"
142
+
143
+ # Configure image generation
144
+ config = GenerateContentConfig(
145
+ image_config=ImageConfig(
146
+ aspect_ratio=aspect_ratio,
147
+ )
148
+ )
149
+
150
+ # Generate image
151
+ response = client.models.generate_content(
152
+ model=model,
153
+ contents=prompt,
154
+ config=config
155
+ )
156
+
157
+ # Extract image data from response
158
+ if response.parts:
159
+ for part in response.parts:
160
+ if hasattr(part, 'inline_data') and part.inline_data:
161
+ usage_metadata = getattr(response, "usage_metadata", {}) or {}
162
+ if usage_metadata and not isinstance(usage_metadata, dict):
163
+ if hasattr(usage_metadata, "model_dump"):
164
+ usage_metadata = usage_metadata.model_dump()
165
+ elif hasattr(usage_metadata, "__dict__"):
166
+ usage_metadata = vars(usage_metadata)
167
+ else:
168
+ try:
169
+ usage_metadata = dict(usage_metadata)
170
+ except (TypeError, ValueError):
171
+ usage_metadata = {}
172
+ return part.inline_data.data
173
+
174
+ raise RuntimeError("No image data in response")
175
+
176
+ except ImportError:
177
+ raise RuntimeError("Google GenAI package not installed. Please install: pip install google-genai")
178
+ except Exception as e:
179
+ raise RuntimeError(f"Failed to generate image with Gemini: {e}")
180
+
181
+ if __name__ == "__main__":
182
+ tool = GenerateImage(
183
+ prompt="A flowchart showing a three-step process: Research -> Design -> Implementation. Use blue boxes and arrows.",
184
+ image_type="concept_art",
185
+ project_name="slides_agent_test_deck",
186
+ asset_name="test_diagram.png",
187
+ style="professional"
188
+ )
189
+ print(tool.run())