@_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,556 @@
1
+ """Video generation tool supporting Sora (OpenAI), Veo (Google Gemini), and Seedance (fal.ai) models."""
2
+
3
+ from typing import Literal, Optional
4
+ import asyncio
5
+ import logging
6
+ import os
7
+ import re
8
+ import mimetypes
9
+ from pathlib import Path
10
+ from urllib.parse import urlparse
11
+
12
+ import fal_client
13
+ import httpx
14
+ from openai import OpenAI
15
+ from pydantic import Field, field_validator, model_validator
16
+ from google.genai.types import GenerateVideosConfig, Image, VideoGenerationReferenceImage
17
+ from PIL import Image as PILImage
18
+ from io import BytesIO
19
+
20
+ from agency_swarm import BaseTool, ToolOutputText
21
+
22
+ from .utils.video_utils import (
23
+ ensure_not_blank,
24
+ extract_last_frame,
25
+ generate_spritesheet,
26
+ get_gemini_client,
27
+ get_videos_dir,
28
+ is_veo_model,
29
+ is_sora_model,
30
+ is_seedance_model,
31
+ resolve_input_reference,
32
+ validate_resolution,
33
+ save_video_with_metadata,
34
+ save_veo_video_with_metadata,
35
+ )
36
+ from .utils.image_utils import load_image_by_name, get_images_dir
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ VIDEO_GENERATION_TIMEOUT_SECONDS = 300
41
+
42
+
43
+ def _is_transient_network_error(exc: Exception) -> bool:
44
+ if isinstance(exc, (BrokenPipeError, ConnectionResetError, TimeoutError)):
45
+ return True
46
+
47
+ message = str(exc).lower()
48
+ transient_markers = (
49
+ "broken pipe",
50
+ "connection reset",
51
+ "timed out",
52
+ "timeout",
53
+ "temporarily unavailable",
54
+ )
55
+ return any(marker in message for marker in transient_markers)
56
+
57
+
58
+ class GenerateVideo(BaseTool):
59
+ """
60
+ Generates a video using OpenAI Sora, Google Veo, or ByteDance Seedance 1.5 Pro (via fal.ai).
61
+
62
+ Tool is stateless and does not maintain any characters / scenes / etc between calls.
63
+ It does not support variables like [INTERNAL PROMPT] in the prompt.
64
+
65
+ **Important**: Sora 2 and Sora 2 Pro do not support reference images with faces.
66
+
67
+ Videos are saved to: mnt/{product_name}/generated_videos/
68
+ """
69
+ product_name: str = Field(
70
+ ...,
71
+ description="Name of the product this video is for (e.g., 'Acme_Widget_Pro', 'Green_Tea_Extract'). Used to organize files into product-specific folders.",
72
+ )
73
+ prompt: str = Field(
74
+ ...,
75
+ description=(
76
+ "Detailed marketing description of the desired video. Include subjects, "
77
+ "camera motion, lighting, and mood for the video generation model. "
78
+ "Be ware that sometimes including things you DONT want to display in the video "
79
+ "can cause the model to generate them instead. Simply don't mention what you don't want to see in the prompt."
80
+ ),
81
+ )
82
+ name: str = Field(
83
+ ...,
84
+ description="The name for the generated video file (without extension)",
85
+ )
86
+ model: Literal[
87
+ "sora-2",
88
+ "sora-2-pro",
89
+ "veo-3.1-generate-preview",
90
+ "veo-3.1-fast-generate-preview",
91
+ "seedance-1.5-pro",
92
+ ] = Field(
93
+ ...,
94
+ description="Video generation model to use.",
95
+ )
96
+ seconds: int = Field(
97
+ default=8,
98
+ ge=4,
99
+ le=12,
100
+ description=(
101
+ "Clip length in seconds. "
102
+ "Sora: 4, 8, or 12. "
103
+ "Veo: 4, 6, or 8. "
104
+ "Seedance 1.5 Pro: any integer 4–12."
105
+ ),
106
+ )
107
+ first_frame_ref: Optional[str] = Field(
108
+ default=None,
109
+ description=(
110
+ "Optional first frame reference image for image-to-video. Can be: "
111
+ "1) Image name without extension (searches generated_images and generated_videos folders), "
112
+ "2) Full local path, or 3) HTTPS URL."
113
+ ),
114
+ )
115
+ asset_image_ref: Optional[str] = Field(
116
+ default=None,
117
+ description=(
118
+ "Optional asset reference image for Veo (subject/asset guidance). Can be: "
119
+ "1) Image name without extension (searches generated_images and generated_videos folders), "
120
+ "2) Full local path, or 3) HTTPS URL."
121
+ ),
122
+ )
123
+ size: Literal['720x1280', '1280x720', '1024x1792', '1792x1024'] = Field(
124
+ default='1280x720',
125
+ description="Optional resolution in WIDTHxHEIGHT format (e.g. 1280x720). For Sora: exact resolution. For Veo: reference image will be cropped/resized to match this aspect ratio to prevent stretching.",
126
+ )
127
+
128
+ @field_validator("prompt")
129
+ @classmethod
130
+ def _prompt_not_blank(cls, value: str) -> str:
131
+ if re.match(r"^\[.*\]", value) or re.match(r"^\[.*?\]\s+.+", value):
132
+ raise ValueError("PROMPT CANNNOT CONTAIN VARIABLES!!! YOU HAVE TO REWRITE THE WHOLE PROMPT FROM SCRATCH!!! THIS TOOL IS STATELESS. STOP BEING LAZY AND REWRITE THE WHOLE PROMPT FOR USER'S FEEDBACK.")
133
+ return ensure_not_blank(value, "prompt")
134
+
135
+ @field_validator("first_frame_ref")
136
+ @classmethod
137
+ def _reference_not_blank(cls, value: Optional[str]) -> Optional[str]:
138
+ if value is not None:
139
+ ensure_not_blank(value, "first_frame_ref")
140
+ return value
141
+
142
+ @field_validator("asset_image_ref")
143
+ @classmethod
144
+ def _asset_reference_not_blank(cls, value: Optional[str]) -> Optional[str]:
145
+ if value is not None:
146
+ ensure_not_blank(value, "asset_image_ref")
147
+ return value
148
+
149
+ @field_validator("size")
150
+ @classmethod
151
+ def _size_format(cls, value: Optional[str]) -> Optional[str]:
152
+ return validate_resolution(value)
153
+
154
+ @model_validator(mode="after")
155
+ def _validate_seconds_for_model(self) -> "GenerateVideo":
156
+ if is_sora_model(self.model) and self.seconds not in {4, 8, 12}:
157
+ raise ValueError("Sora supports only 4, 8, or 12 second clips.")
158
+ if is_veo_model(self.model) and self.seconds not in {4, 6, 8}:
159
+ raise ValueError("Veo supports only 4, 6, or 8 second clips.")
160
+ if is_sora_model(self.model) and self.asset_image_ref is not None:
161
+ raise ValueError("Sora does not support asset_image_ref. Use first_frame_ref instead.")
162
+ if is_seedance_model(self.model) and self.asset_image_ref is not None:
163
+ raise ValueError("Seedance does not support asset_image_ref. Use first_frame_ref instead.")
164
+ return self
165
+
166
+ async def run(self) -> list:
167
+ """Generate a marketing video using the chosen model."""
168
+ if is_seedance_model(self.model):
169
+ return await self._generate_with_seedance(self.model)
170
+
171
+ if is_sora_model(self.model):
172
+ return await self._generate_with_sora(self.model)
173
+
174
+ if is_veo_model(self.model):
175
+ return await self._generate_with_veo(self.model)
176
+
177
+ raise ValueError(f"Unsupported video model: {self.model}")
178
+
179
+ async def _generate_with_sora(self, model: str) -> dict:
180
+ """Generate video using OpenAI's Sora API."""
181
+
182
+ api_key = os.getenv("OPENAI_API_KEY")
183
+ if not api_key:
184
+ raise RuntimeError(
185
+ "OPENAI_API_KEY environment variable is required for video operations"
186
+ )
187
+
188
+ client: OpenAI = OpenAI(api_key=api_key)
189
+ reference_file = None
190
+
191
+ try:
192
+ reference_file = resolve_input_reference(
193
+ self.first_frame_ref,
194
+ target_size=self.size if self.first_frame_ref else None,
195
+ product_name=self.product_name
196
+ )
197
+
198
+ request_payload = {
199
+ "prompt": self.prompt,
200
+ "model": model,
201
+ "seconds": str(self.seconds),
202
+ }
203
+ if self.size:
204
+ request_payload["size"] = self.size
205
+ if reference_file is not None:
206
+ request_payload["input_reference"] = reference_file
207
+
208
+ logger.info(f"Submitting video generation request to Sora ({model})...")
209
+
210
+ # Run blocking operation in thread pool to avoid blocking event loop
211
+ loop = asyncio.get_event_loop()
212
+ try:
213
+ video = await loop.run_in_executor(
214
+ None,
215
+ lambda: client.videos.create(**request_payload)
216
+ )
217
+ except Exception as exc:
218
+ if _is_transient_network_error(exc):
219
+ raise RuntimeError(
220
+ f"Sora submission hit a transient network error: {exc}. "
221
+ "Please retry this GenerateVideo call."
222
+ ) from exc
223
+ raise
224
+
225
+ started_at = asyncio.get_running_loop().time()
226
+ while getattr(video, "status", None) not in {"completed", "failed", "cancelled"}:
227
+ logger.info("Waiting for Sora video generation to complete...")
228
+ elapsed = asyncio.get_running_loop().time() - started_at
229
+ if elapsed > VIDEO_GENERATION_TIMEOUT_SECONDS:
230
+ raise RuntimeError(
231
+ f"Sora video generation timed out after {VIDEO_GENERATION_TIMEOUT_SECONDS} seconds. Openai server is likely overloaded."
232
+ "Please try again later or use a different model."
233
+ )
234
+ await asyncio.sleep(10)
235
+ try:
236
+ video = await loop.run_in_executor(
237
+ None,
238
+ lambda: client.videos.retrieve(video.id)
239
+ )
240
+ except Exception as exc:
241
+ if not _is_transient_network_error(exc):
242
+ raise
243
+ logger.warning(f"Transient Sora polling error: {exc}. Retrying same video id...")
244
+
245
+ logger.info(f"Video generation status: {video.status}")
246
+ if video.status != "completed":
247
+ raise RuntimeError(
248
+ f"Sora video generation ended with status: {video.status}. Please retry."
249
+ )
250
+
251
+ return save_video_with_metadata(client, video.id, self.name, self.product_name)
252
+
253
+ finally:
254
+ if reference_file is not None and hasattr(reference_file, "close"):
255
+ try:
256
+ reference_file.close()
257
+ except Exception:
258
+ pass
259
+
260
+ async def _generate_with_veo(self, model: str) -> dict:
261
+ """Generate video using Google's Veo API with optional references."""
262
+
263
+ client = get_gemini_client()
264
+
265
+ try:
266
+ config_kwargs = {"duration_seconds": self.seconds}
267
+ first_frame_image = None
268
+
269
+ # Add aspect_ratio and resolution only when NOT using reference images
270
+ # (these parameters cause "not supported" errors when used with reference images)
271
+ if self.size and not self.first_frame_ref and not self.asset_image_ref:
272
+ width, height = map(int, self.size.split('x'))
273
+ config_kwargs["aspect_ratio"] = "9:16" if width < height else "16:9"
274
+
275
+ if self.asset_image_ref:
276
+ parsed = urlparse(self.asset_image_ref)
277
+
278
+ if parsed.scheme in ("http", "https"):
279
+ raise ValueError("Veo does not support URL reference images. Please use local images.")
280
+ else:
281
+ path = Path(self.asset_image_ref).expanduser().resolve()
282
+ if path.exists():
283
+ image_path = str(path)
284
+ else:
285
+ images_dir = get_images_dir(self.product_name)
286
+ pil_image, image_path, load_error = load_image_by_name(
287
+ self.asset_image_ref, images_dir, [".png", ".jpg", ".jpeg", ".webp"]
288
+ )
289
+ if load_error:
290
+ raise FileNotFoundError(f"Reference image '{self.asset_image_ref}' not found in {images_dir}")
291
+
292
+ logger.info(f"Loading asset reference image for Veo: {image_path}")
293
+
294
+ with PILImage.open(image_path) as img:
295
+ if img.mode != 'RGB':
296
+ img = img.convert('RGB')
297
+
298
+ if self.size:
299
+ target_width, target_height = map(int, self.size.split('x'))
300
+ target_ratio = target_width / target_height
301
+ img_ratio = img.width / img.height
302
+
303
+ # Crop to match aspect ratio (center crop)
304
+ if img_ratio > target_ratio:
305
+ # Image is wider, crop width
306
+ new_width = int(img.height * target_ratio)
307
+ left = (img.width - new_width) // 2
308
+ img = img.crop((left, 0, left + new_width, img.height))
309
+ elif img_ratio < target_ratio:
310
+ # Image is taller, crop height
311
+ new_height = int(img.width / target_ratio)
312
+ top = (img.height - new_height) // 2
313
+ img = img.crop((0, top, img.width, top + new_height))
314
+
315
+ img = img.resize((target_width, target_height), PILImage.Resampling.LANCZOS)
316
+
317
+ buffer = BytesIO()
318
+ img.save(buffer, format='PNG')
319
+ image_bytes = buffer.getvalue()
320
+ mime_type = "image/png"
321
+
322
+ config_kwargs["reference_images"] = [
323
+ VideoGenerationReferenceImage(
324
+ image=Image(
325
+ image_bytes=image_bytes,
326
+ mime_type=mime_type,
327
+ ),
328
+ reference_type="asset",
329
+ ),
330
+ ]
331
+
332
+ if self.first_frame_ref:
333
+ # For first frame, pass as 'image' parameter directly to generate_videos()
334
+ # According to official docs: https://docs.cloud.google.com/vertex-ai/generative-ai/docs/video/generate-videos-from-an-image
335
+ reference_file = resolve_input_reference(
336
+ self.first_frame_ref,
337
+ target_size=self.size if self.first_frame_ref else None,
338
+ product_name=self.product_name,
339
+ )
340
+ first_frame_bytes = reference_file.read()
341
+ mime_type = "image/png"
342
+ if getattr(reference_file, "name", None):
343
+ guessed = mimetypes.guess_type(reference_file.name)[0]
344
+ if guessed:
345
+ mime_type = guessed
346
+
347
+ first_frame_image = Image(
348
+ image_bytes=first_frame_bytes,
349
+ mime_type=mime_type,
350
+ )
351
+
352
+ if hasattr(reference_file, "close"):
353
+ try:
354
+ reference_file.close()
355
+ except Exception:
356
+ pass
357
+
358
+ config = GenerateVideosConfig(**config_kwargs)
359
+
360
+ if self.size and self.first_frame_ref:
361
+ logger.info(f"Submitting video generation request to Veo ({model}) - first frame resized to {self.size} (aspect ratio inferred from image)...")
362
+ elif self.size:
363
+ logger.info(f"Submitting video generation request to Veo ({model}) - size: {self.size}...")
364
+ else:
365
+ logger.info(f"Submitting video generation request to Veo ({model})...")
366
+
367
+ # run_in_executor keeps the event loop free while Veo polls
368
+ loop = asyncio.get_event_loop()
369
+ generate_kwargs = {
370
+ "model": model,
371
+ "prompt": self.prompt,
372
+ "config": config,
373
+ }
374
+
375
+ # Add image parameter if first_frame_ref is provided (simple image-to-video)
376
+ if first_frame_image:
377
+ generate_kwargs["image"] = first_frame_image
378
+
379
+ started_at = asyncio.get_running_loop().time()
380
+ try:
381
+ operation = await loop.run_in_executor(
382
+ None,
383
+ lambda: client.models.generate_videos(**generate_kwargs)
384
+ )
385
+ except Exception as exc:
386
+ if _is_transient_network_error(exc):
387
+ raise RuntimeError(
388
+ f"Veo submission hit a transient network error: {exc}. "
389
+ "Please retry this GenerateVideo call."
390
+ ) from exc
391
+ raise
392
+
393
+ # Poll the operation status until the video is ready
394
+ while not operation.done:
395
+ logger.info("Waiting for Veo video generation to complete...")
396
+ elapsed = asyncio.get_running_loop().time() - started_at
397
+ if elapsed > VIDEO_GENERATION_TIMEOUT_SECONDS:
398
+ raise RuntimeError(
399
+ f"Veo video generation timed out after {VIDEO_GENERATION_TIMEOUT_SECONDS} seconds."
400
+ )
401
+ await asyncio.sleep(10)
402
+ try:
403
+ operation = await loop.run_in_executor(
404
+ None,
405
+ lambda: client.operations.get(operation)
406
+ )
407
+ except Exception as exc:
408
+ if not _is_transient_network_error(exc):
409
+ raise
410
+ logger.warning(f"Transient Veo polling error: {exc}. Retrying same operation...")
411
+
412
+ logger.info("Video generation complete!")
413
+
414
+ # Download the generated video — retry on transient network errors
415
+ generated_video = operation.response.generated_videos[0]
416
+ MAX_DOWNLOAD_RETRIES = 3
417
+ last_exc: Exception | None = None
418
+ for attempt in range(MAX_DOWNLOAD_RETRIES):
419
+ try:
420
+ output = await loop.run_in_executor(
421
+ None,
422
+ lambda: save_veo_video_with_metadata(
423
+ client, generated_video.video, self.name, self.product_name
424
+ ),
425
+ )
426
+ last_exc = None
427
+ break
428
+ except Exception as exc:
429
+ last_exc = exc
430
+ if attempt < MAX_DOWNLOAD_RETRIES - 1 and _is_transient_network_error(exc):
431
+ wait = 5 * (attempt + 1)
432
+ logger.warning(
433
+ f"Transient Veo download error (attempt {attempt + 1}/{MAX_DOWNLOAD_RETRIES}): "
434
+ f"{exc}. Retrying in {wait}s..."
435
+ )
436
+ await asyncio.sleep(wait)
437
+ else:
438
+ raise
439
+ if last_exc is not None:
440
+ raise last_exc
441
+
442
+ return output
443
+
444
+ except RuntimeError:
445
+ raise
446
+ except Exception as e:
447
+ raise RuntimeError(f"Veo video generation failed: {str(e)}")
448
+
449
+ async def _generate_with_seedance(self, model: str) -> list:
450
+ """Generate video using ByteDance Seedance 1.5 Pro via fal.ai."""
451
+ api_key = os.getenv("FAL_KEY")
452
+ if not api_key:
453
+ raise ValueError("FAL_KEY is not set. Add it to your .env to use video generation.")
454
+ fal = fal_client.SyncClient(key=api_key)
455
+
456
+ duration = str(self.seconds)
457
+
458
+ # Map size → fal.ai resolution label and aspect ratio
459
+ width, height = map(int, self.size.split("x"))
460
+ max_dim = max(width, height)
461
+ if max_dim >= 1080:
462
+ resolution = "1080p"
463
+ elif max_dim >= 720:
464
+ resolution = "720p"
465
+ else:
466
+ resolution = "480p"
467
+
468
+ if width < height:
469
+ aspect_ratio = "9:16"
470
+ elif width > height:
471
+ aspect_ratio = "16:9"
472
+ else:
473
+ aspect_ratio = "1:1"
474
+
475
+ if self.first_frame_ref:
476
+ endpoint = "fal-ai/bytedance/seedance/v1.5/pro/image-to-video"
477
+ image_url = await self._resolve_image_for_fal(self.first_frame_ref, fal)
478
+ arguments: dict = {
479
+ "prompt": self.prompt,
480
+ "image_url": image_url,
481
+ "duration": duration,
482
+ "resolution": resolution,
483
+ }
484
+ else:
485
+ endpoint = "fal-ai/bytedance/seedance/v1.5/pro/text-to-video"
486
+ arguments = {
487
+ "prompt": self.prompt,
488
+ "duration": duration,
489
+ "resolution": resolution,
490
+ "aspect_ratio": aspect_ratio,
491
+ }
492
+
493
+ logger.info(f"Submitting video generation request to Seedance 1.5 Pro (fal.ai, {endpoint})...")
494
+ result = await asyncio.to_thread(
495
+ fal.subscribe, endpoint, arguments=arguments, with_logs=True
496
+ )
497
+
498
+ output_url = (result.get("video") or {}).get("url")
499
+ if not output_url:
500
+ raise RuntimeError(f"Seedance did not return a video URL. Response: {result}")
501
+
502
+ videos_dir = get_videos_dir(self.product_name)
503
+ output_path = os.path.join(videos_dir, f"{self.name}.mp4")
504
+
505
+ logger.info(f"Downloading Seedance video to {output_path}...")
506
+ async with httpx.AsyncClient(timeout=120.0) as http:
507
+ response = await http.get(output_url)
508
+ response.raise_for_status()
509
+ with open(output_path, "wb") as fh:
510
+ fh.write(response.content)
511
+
512
+ spritesheet_path = os.path.join(videos_dir, f"{self.name}_spritesheet.jpg")
513
+ await asyncio.to_thread(generate_spritesheet, output_path, spritesheet_path)
514
+
515
+ last_frame_path = os.path.join(videos_dir, f"{self.name}_last_frame.jpg")
516
+ await asyncio.to_thread(extract_last_frame, output_path, last_frame_path)
517
+
518
+ return [ToolOutputText(type="text", text=f"Video saved to `{self.name}.mp4`\nPath: {output_path}")]
519
+
520
+ async def _resolve_image_for_fal(self, image_ref: str, fal: fal_client.SyncClient) -> str:
521
+ """Resolve a local path or image name to a fal.ai-accessible URL."""
522
+ parsed = urlparse(image_ref)
523
+ if parsed.scheme in ("http", "https"):
524
+ return image_ref
525
+
526
+ path = Path(image_ref).expanduser().resolve()
527
+ if not path.exists():
528
+ images_dir = get_images_dir(self.product_name)
529
+ _, image_path, err = load_image_by_name(
530
+ image_ref, images_dir, [".png", ".jpg", ".jpeg", ".webp"]
531
+ )
532
+ if err:
533
+ raise FileNotFoundError(
534
+ f"Reference image '{image_ref}' not found in {images_dir}"
535
+ )
536
+ path = Path(image_path)
537
+
538
+ return await asyncio.to_thread(fal.upload_file, str(path))
539
+
540
+ if __name__ == "__main__":
541
+ # Basic test invocation (Sora)
542
+ tool = GenerateVideo(
543
+ product_name = "bird_forest_veo",
544
+ prompt = "Cinematic nature footage: a small colorful songbird (robin-like) flies swiftly through a dense evergreen forest corridor, weaving between mossy trunks and sunlit branches. The camera follows in a smooth tracking shot at bird height, slightly behind and to the side, maintaining the bird in sharp focus while the background streaks with gentle motion blur. Early morning golden light filters through the canopy, creating volumetric rays and floating dust motes; rich greens and warm highlights, high dynamic range, natural filmic contrast. The bird beats its wings rhythmically, occasionally gliding past ferns and hanging vines; leaves flutter from the air wake. Lens: 35mm, shallow depth of field, stabilized gimbal-like motion, realistic textures and feathers. Audio: clear forest ambience with soft wind through needles, subtle wing flaps, distant birdsong.",
545
+ name = "bird_flying_forest_4s_16x9_fast_v2",
546
+ model = "veo-3.1-fast-generate-preview",
547
+ seconds = 4,
548
+ size = "1280x720"
549
+
550
+ )
551
+ try:
552
+ logging.basicConfig(level=logging.INFO)
553
+ result = asyncio.run(tool.run())
554
+ print(result)
555
+ except Exception as exc:
556
+ print(f"Video generation failed: {exc}")