ai-parrot 0.17.2__cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

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 (535) hide show
  1. agentui/.prettierrc +15 -0
  2. agentui/QUICKSTART.md +272 -0
  3. agentui/README.md +59 -0
  4. agentui/env.example +16 -0
  5. agentui/jsconfig.json +14 -0
  6. agentui/package-lock.json +4242 -0
  7. agentui/package.json +34 -0
  8. agentui/scripts/postinstall/apply-patches.mjs +260 -0
  9. agentui/src/app.css +61 -0
  10. agentui/src/app.d.ts +13 -0
  11. agentui/src/app.html +12 -0
  12. agentui/src/components/LoadingSpinner.svelte +64 -0
  13. agentui/src/components/ThemeSwitcher.svelte +159 -0
  14. agentui/src/components/index.js +4 -0
  15. agentui/src/lib/api/bots.ts +60 -0
  16. agentui/src/lib/api/chat.ts +22 -0
  17. agentui/src/lib/api/http.ts +25 -0
  18. agentui/src/lib/components/BotCard.svelte +33 -0
  19. agentui/src/lib/components/ChatBubble.svelte +63 -0
  20. agentui/src/lib/components/Toast.svelte +21 -0
  21. agentui/src/lib/config.ts +20 -0
  22. agentui/src/lib/stores/auth.svelte.ts +73 -0
  23. agentui/src/lib/stores/theme.svelte.js +64 -0
  24. agentui/src/lib/stores/toast.svelte.ts +31 -0
  25. agentui/src/lib/utils/conversation.ts +39 -0
  26. agentui/src/routes/+layout.svelte +20 -0
  27. agentui/src/routes/+page.svelte +232 -0
  28. agentui/src/routes/login/+page.svelte +200 -0
  29. agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
  30. agentui/src/routes/talk/[agentId]/+page.ts +7 -0
  31. agentui/static/README.md +1 -0
  32. agentui/svelte.config.js +11 -0
  33. agentui/tailwind.config.ts +53 -0
  34. agentui/tsconfig.json +3 -0
  35. agentui/vite.config.ts +10 -0
  36. ai_parrot-0.17.2.dist-info/METADATA +472 -0
  37. ai_parrot-0.17.2.dist-info/RECORD +535 -0
  38. ai_parrot-0.17.2.dist-info/WHEEL +6 -0
  39. ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
  40. ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
  41. ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
  42. crew-builder/.prettierrc +15 -0
  43. crew-builder/QUICKSTART.md +259 -0
  44. crew-builder/README.md +113 -0
  45. crew-builder/env.example +17 -0
  46. crew-builder/jsconfig.json +14 -0
  47. crew-builder/package-lock.json +4182 -0
  48. crew-builder/package.json +37 -0
  49. crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
  50. crew-builder/src/app.css +62 -0
  51. crew-builder/src/app.d.ts +13 -0
  52. crew-builder/src/app.html +12 -0
  53. crew-builder/src/components/LoadingSpinner.svelte +64 -0
  54. crew-builder/src/components/ThemeSwitcher.svelte +149 -0
  55. crew-builder/src/components/index.js +9 -0
  56. crew-builder/src/lib/api/bots.ts +60 -0
  57. crew-builder/src/lib/api/chat.ts +80 -0
  58. crew-builder/src/lib/api/client.ts +56 -0
  59. crew-builder/src/lib/api/crew/crew.ts +136 -0
  60. crew-builder/src/lib/api/index.ts +5 -0
  61. crew-builder/src/lib/api/o365/auth.ts +65 -0
  62. crew-builder/src/lib/auth/auth.ts +54 -0
  63. crew-builder/src/lib/components/AgentNode.svelte +43 -0
  64. crew-builder/src/lib/components/BotCard.svelte +33 -0
  65. crew-builder/src/lib/components/ChatBubble.svelte +67 -0
  66. crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
  67. crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
  68. crew-builder/src/lib/components/JsonViewer.svelte +24 -0
  69. crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
  70. crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
  71. crew-builder/src/lib/components/Toast.svelte +67 -0
  72. crew-builder/src/lib/components/Toolbar.svelte +157 -0
  73. crew-builder/src/lib/components/index.ts +10 -0
  74. crew-builder/src/lib/config.ts +8 -0
  75. crew-builder/src/lib/stores/auth.svelte.ts +228 -0
  76. crew-builder/src/lib/stores/crewStore.ts +369 -0
  77. crew-builder/src/lib/stores/theme.svelte.js +145 -0
  78. crew-builder/src/lib/stores/toast.svelte.ts +69 -0
  79. crew-builder/src/lib/utils/conversation.ts +39 -0
  80. crew-builder/src/lib/utils/markdown.ts +122 -0
  81. crew-builder/src/lib/utils/talkHistory.ts +47 -0
  82. crew-builder/src/routes/+layout.svelte +20 -0
  83. crew-builder/src/routes/+page.svelte +539 -0
  84. crew-builder/src/routes/agents/+page.svelte +247 -0
  85. crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
  86. crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
  87. crew-builder/src/routes/builder/+page.svelte +204 -0
  88. crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
  89. crew-builder/src/routes/crew/ask/+page.ts +1 -0
  90. crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
  91. crew-builder/src/routes/login/+page.svelte +197 -0
  92. crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
  93. crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
  94. crew-builder/static/README.md +1 -0
  95. crew-builder/svelte.config.js +11 -0
  96. crew-builder/tailwind.config.ts +53 -0
  97. crew-builder/tsconfig.json +3 -0
  98. crew-builder/vite.config.ts +10 -0
  99. mcp_servers/calculator_server.py +309 -0
  100. parrot/__init__.py +27 -0
  101. parrot/__pycache__/__init__.cpython-310.pyc +0 -0
  102. parrot/__pycache__/version.cpython-310.pyc +0 -0
  103. parrot/_version.py +34 -0
  104. parrot/a2a/__init__.py +48 -0
  105. parrot/a2a/client.py +658 -0
  106. parrot/a2a/discovery.py +89 -0
  107. parrot/a2a/mixin.py +257 -0
  108. parrot/a2a/models.py +376 -0
  109. parrot/a2a/server.py +770 -0
  110. parrot/agents/__init__.py +29 -0
  111. parrot/bots/__init__.py +12 -0
  112. parrot/bots/a2a_agent.py +19 -0
  113. parrot/bots/abstract.py +3139 -0
  114. parrot/bots/agent.py +1129 -0
  115. parrot/bots/basic.py +9 -0
  116. parrot/bots/chatbot.py +669 -0
  117. parrot/bots/data.py +1618 -0
  118. parrot/bots/database/__init__.py +5 -0
  119. parrot/bots/database/abstract.py +3071 -0
  120. parrot/bots/database/cache.py +286 -0
  121. parrot/bots/database/models.py +468 -0
  122. parrot/bots/database/prompts.py +154 -0
  123. parrot/bots/database/retries.py +98 -0
  124. parrot/bots/database/router.py +269 -0
  125. parrot/bots/database/sql.py +41 -0
  126. parrot/bots/db/__init__.py +6 -0
  127. parrot/bots/db/abstract.py +556 -0
  128. parrot/bots/db/bigquery.py +602 -0
  129. parrot/bots/db/cache.py +85 -0
  130. parrot/bots/db/documentdb.py +668 -0
  131. parrot/bots/db/elastic.py +1014 -0
  132. parrot/bots/db/influx.py +898 -0
  133. parrot/bots/db/mock.py +96 -0
  134. parrot/bots/db/multi.py +783 -0
  135. parrot/bots/db/prompts.py +185 -0
  136. parrot/bots/db/sql.py +1255 -0
  137. parrot/bots/db/tools.py +212 -0
  138. parrot/bots/document.py +680 -0
  139. parrot/bots/hrbot.py +15 -0
  140. parrot/bots/kb.py +170 -0
  141. parrot/bots/mcp.py +36 -0
  142. parrot/bots/orchestration/README.md +463 -0
  143. parrot/bots/orchestration/__init__.py +1 -0
  144. parrot/bots/orchestration/agent.py +155 -0
  145. parrot/bots/orchestration/crew.py +3330 -0
  146. parrot/bots/orchestration/fsm.py +1179 -0
  147. parrot/bots/orchestration/hr.py +434 -0
  148. parrot/bots/orchestration/storage/__init__.py +4 -0
  149. parrot/bots/orchestration/storage/memory.py +100 -0
  150. parrot/bots/orchestration/storage/mixin.py +119 -0
  151. parrot/bots/orchestration/verify.py +202 -0
  152. parrot/bots/product.py +204 -0
  153. parrot/bots/prompts/__init__.py +96 -0
  154. parrot/bots/prompts/agents.py +155 -0
  155. parrot/bots/prompts/data.py +216 -0
  156. parrot/bots/prompts/output_generation.py +8 -0
  157. parrot/bots/scraper/__init__.py +3 -0
  158. parrot/bots/scraper/models.py +122 -0
  159. parrot/bots/scraper/scraper.py +1173 -0
  160. parrot/bots/scraper/templates.py +115 -0
  161. parrot/bots/stores/__init__.py +5 -0
  162. parrot/bots/stores/local.py +172 -0
  163. parrot/bots/webdev.py +81 -0
  164. parrot/cli.py +17 -0
  165. parrot/clients/__init__.py +16 -0
  166. parrot/clients/base.py +1491 -0
  167. parrot/clients/claude.py +1191 -0
  168. parrot/clients/factory.py +129 -0
  169. parrot/clients/google.py +4567 -0
  170. parrot/clients/gpt.py +1975 -0
  171. parrot/clients/grok.py +432 -0
  172. parrot/clients/groq.py +986 -0
  173. parrot/clients/hf.py +582 -0
  174. parrot/clients/models.py +18 -0
  175. parrot/conf.py +395 -0
  176. parrot/embeddings/__init__.py +9 -0
  177. parrot/embeddings/base.py +157 -0
  178. parrot/embeddings/google.py +98 -0
  179. parrot/embeddings/huggingface.py +74 -0
  180. parrot/embeddings/openai.py +84 -0
  181. parrot/embeddings/processor.py +88 -0
  182. parrot/exceptions.c +13868 -0
  183. parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
  184. parrot/exceptions.pxd +22 -0
  185. parrot/exceptions.pxi +15 -0
  186. parrot/exceptions.pyx +44 -0
  187. parrot/generators/__init__.py +29 -0
  188. parrot/generators/base.py +200 -0
  189. parrot/generators/html.py +293 -0
  190. parrot/generators/react.py +205 -0
  191. parrot/generators/streamlit.py +203 -0
  192. parrot/generators/template.py +105 -0
  193. parrot/handlers/__init__.py +4 -0
  194. parrot/handlers/agent.py +861 -0
  195. parrot/handlers/agents/__init__.py +1 -0
  196. parrot/handlers/agents/abstract.py +900 -0
  197. parrot/handlers/bots.py +338 -0
  198. parrot/handlers/chat.py +915 -0
  199. parrot/handlers/creation.sql +192 -0
  200. parrot/handlers/crew/ARCHITECTURE.md +362 -0
  201. parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
  202. parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
  203. parrot/handlers/crew/__init__.py +0 -0
  204. parrot/handlers/crew/handler.py +801 -0
  205. parrot/handlers/crew/models.py +229 -0
  206. parrot/handlers/crew/redis_persistence.py +523 -0
  207. parrot/handlers/jobs/__init__.py +10 -0
  208. parrot/handlers/jobs/job.py +384 -0
  209. parrot/handlers/jobs/mixin.py +627 -0
  210. parrot/handlers/jobs/models.py +115 -0
  211. parrot/handlers/jobs/worker.py +31 -0
  212. parrot/handlers/models.py +596 -0
  213. parrot/handlers/o365_auth.py +105 -0
  214. parrot/handlers/stream.py +337 -0
  215. parrot/interfaces/__init__.py +6 -0
  216. parrot/interfaces/aws.py +143 -0
  217. parrot/interfaces/credentials.py +113 -0
  218. parrot/interfaces/database.py +27 -0
  219. parrot/interfaces/google.py +1123 -0
  220. parrot/interfaces/hierarchy.py +1227 -0
  221. parrot/interfaces/http.py +651 -0
  222. parrot/interfaces/images/__init__.py +0 -0
  223. parrot/interfaces/images/plugins/__init__.py +24 -0
  224. parrot/interfaces/images/plugins/abstract.py +58 -0
  225. parrot/interfaces/images/plugins/analisys.py +148 -0
  226. parrot/interfaces/images/plugins/classify.py +150 -0
  227. parrot/interfaces/images/plugins/classifybase.py +182 -0
  228. parrot/interfaces/images/plugins/detect.py +150 -0
  229. parrot/interfaces/images/plugins/exif.py +1103 -0
  230. parrot/interfaces/images/plugins/hash.py +52 -0
  231. parrot/interfaces/images/plugins/vision.py +104 -0
  232. parrot/interfaces/images/plugins/yolo.py +66 -0
  233. parrot/interfaces/images/plugins/zerodetect.py +197 -0
  234. parrot/interfaces/o365.py +978 -0
  235. parrot/interfaces/onedrive.py +822 -0
  236. parrot/interfaces/sharepoint.py +1435 -0
  237. parrot/interfaces/soap.py +257 -0
  238. parrot/loaders/__init__.py +8 -0
  239. parrot/loaders/abstract.py +1131 -0
  240. parrot/loaders/audio.py +199 -0
  241. parrot/loaders/basepdf.py +53 -0
  242. parrot/loaders/basevideo.py +1568 -0
  243. parrot/loaders/csv.py +409 -0
  244. parrot/loaders/docx.py +116 -0
  245. parrot/loaders/epubloader.py +316 -0
  246. parrot/loaders/excel.py +199 -0
  247. parrot/loaders/factory.py +55 -0
  248. parrot/loaders/files/__init__.py +0 -0
  249. parrot/loaders/files/abstract.py +39 -0
  250. parrot/loaders/files/html.py +26 -0
  251. parrot/loaders/files/text.py +63 -0
  252. parrot/loaders/html.py +152 -0
  253. parrot/loaders/markdown.py +442 -0
  254. parrot/loaders/pdf.py +373 -0
  255. parrot/loaders/pdfmark.py +320 -0
  256. parrot/loaders/pdftables.py +506 -0
  257. parrot/loaders/ppt.py +476 -0
  258. parrot/loaders/qa.py +63 -0
  259. parrot/loaders/splitters/__init__.py +10 -0
  260. parrot/loaders/splitters/base.py +138 -0
  261. parrot/loaders/splitters/md.py +228 -0
  262. parrot/loaders/splitters/token.py +143 -0
  263. parrot/loaders/txt.py +26 -0
  264. parrot/loaders/video.py +89 -0
  265. parrot/loaders/videolocal.py +218 -0
  266. parrot/loaders/videounderstanding.py +377 -0
  267. parrot/loaders/vimeo.py +167 -0
  268. parrot/loaders/web.py +599 -0
  269. parrot/loaders/youtube.py +504 -0
  270. parrot/manager/__init__.py +5 -0
  271. parrot/manager/manager.py +1030 -0
  272. parrot/mcp/__init__.py +28 -0
  273. parrot/mcp/adapter.py +105 -0
  274. parrot/mcp/cli.py +174 -0
  275. parrot/mcp/client.py +119 -0
  276. parrot/mcp/config.py +75 -0
  277. parrot/mcp/integration.py +842 -0
  278. parrot/mcp/oauth.py +933 -0
  279. parrot/mcp/server.py +225 -0
  280. parrot/mcp/transports/__init__.py +3 -0
  281. parrot/mcp/transports/base.py +279 -0
  282. parrot/mcp/transports/grpc_session.py +163 -0
  283. parrot/mcp/transports/http.py +312 -0
  284. parrot/mcp/transports/mcp.proto +108 -0
  285. parrot/mcp/transports/quic.py +1082 -0
  286. parrot/mcp/transports/sse.py +330 -0
  287. parrot/mcp/transports/stdio.py +309 -0
  288. parrot/mcp/transports/unix.py +395 -0
  289. parrot/mcp/transports/websocket.py +547 -0
  290. parrot/memory/__init__.py +16 -0
  291. parrot/memory/abstract.py +209 -0
  292. parrot/memory/agent.py +32 -0
  293. parrot/memory/cache.py +175 -0
  294. parrot/memory/core.py +555 -0
  295. parrot/memory/file.py +153 -0
  296. parrot/memory/mem.py +131 -0
  297. parrot/memory/redis.py +613 -0
  298. parrot/models/__init__.py +46 -0
  299. parrot/models/basic.py +118 -0
  300. parrot/models/compliance.py +208 -0
  301. parrot/models/crew.py +395 -0
  302. parrot/models/detections.py +654 -0
  303. parrot/models/generation.py +85 -0
  304. parrot/models/google.py +223 -0
  305. parrot/models/groq.py +23 -0
  306. parrot/models/openai.py +30 -0
  307. parrot/models/outputs.py +285 -0
  308. parrot/models/responses.py +938 -0
  309. parrot/notifications/__init__.py +743 -0
  310. parrot/openapi/__init__.py +3 -0
  311. parrot/openapi/components.yaml +641 -0
  312. parrot/openapi/config.py +322 -0
  313. parrot/outputs/__init__.py +32 -0
  314. parrot/outputs/formats/__init__.py +108 -0
  315. parrot/outputs/formats/altair.py +359 -0
  316. parrot/outputs/formats/application.py +122 -0
  317. parrot/outputs/formats/base.py +351 -0
  318. parrot/outputs/formats/bokeh.py +356 -0
  319. parrot/outputs/formats/card.py +424 -0
  320. parrot/outputs/formats/chart.py +436 -0
  321. parrot/outputs/formats/d3.py +255 -0
  322. parrot/outputs/formats/echarts.py +310 -0
  323. parrot/outputs/formats/generators/__init__.py +0 -0
  324. parrot/outputs/formats/generators/abstract.py +61 -0
  325. parrot/outputs/formats/generators/panel.py +145 -0
  326. parrot/outputs/formats/generators/streamlit.py +86 -0
  327. parrot/outputs/formats/generators/terminal.py +63 -0
  328. parrot/outputs/formats/holoviews.py +310 -0
  329. parrot/outputs/formats/html.py +147 -0
  330. parrot/outputs/formats/jinja2.py +46 -0
  331. parrot/outputs/formats/json.py +87 -0
  332. parrot/outputs/formats/map.py +933 -0
  333. parrot/outputs/formats/markdown.py +172 -0
  334. parrot/outputs/formats/matplotlib.py +237 -0
  335. parrot/outputs/formats/mixins/__init__.py +0 -0
  336. parrot/outputs/formats/mixins/emaps.py +855 -0
  337. parrot/outputs/formats/plotly.py +341 -0
  338. parrot/outputs/formats/seaborn.py +310 -0
  339. parrot/outputs/formats/table.py +397 -0
  340. parrot/outputs/formats/template_report.py +138 -0
  341. parrot/outputs/formats/yaml.py +125 -0
  342. parrot/outputs/formatter.py +152 -0
  343. parrot/outputs/templates/__init__.py +95 -0
  344. parrot/pipelines/__init__.py +0 -0
  345. parrot/pipelines/abstract.py +210 -0
  346. parrot/pipelines/detector.py +124 -0
  347. parrot/pipelines/models.py +90 -0
  348. parrot/pipelines/planogram.py +3002 -0
  349. parrot/pipelines/table.sql +97 -0
  350. parrot/plugins/__init__.py +106 -0
  351. parrot/plugins/importer.py +80 -0
  352. parrot/py.typed +0 -0
  353. parrot/registry/__init__.py +18 -0
  354. parrot/registry/registry.py +594 -0
  355. parrot/scheduler/__init__.py +1189 -0
  356. parrot/scheduler/models.py +60 -0
  357. parrot/security/__init__.py +16 -0
  358. parrot/security/prompt_injection.py +268 -0
  359. parrot/security/security_events.sql +25 -0
  360. parrot/services/__init__.py +1 -0
  361. parrot/services/mcp/__init__.py +8 -0
  362. parrot/services/mcp/config.py +13 -0
  363. parrot/services/mcp/server.py +295 -0
  364. parrot/services/o365_remote_auth.py +235 -0
  365. parrot/stores/__init__.py +7 -0
  366. parrot/stores/abstract.py +352 -0
  367. parrot/stores/arango.py +1090 -0
  368. parrot/stores/bigquery.py +1377 -0
  369. parrot/stores/cache.py +106 -0
  370. parrot/stores/empty.py +10 -0
  371. parrot/stores/faiss_store.py +1157 -0
  372. parrot/stores/kb/__init__.py +9 -0
  373. parrot/stores/kb/abstract.py +68 -0
  374. parrot/stores/kb/cache.py +165 -0
  375. parrot/stores/kb/doc.py +325 -0
  376. parrot/stores/kb/hierarchy.py +346 -0
  377. parrot/stores/kb/local.py +457 -0
  378. parrot/stores/kb/prompt.py +28 -0
  379. parrot/stores/kb/redis.py +659 -0
  380. parrot/stores/kb/store.py +115 -0
  381. parrot/stores/kb/user.py +374 -0
  382. parrot/stores/models.py +59 -0
  383. parrot/stores/pgvector.py +3 -0
  384. parrot/stores/postgres.py +2853 -0
  385. parrot/stores/utils/__init__.py +0 -0
  386. parrot/stores/utils/chunking.py +197 -0
  387. parrot/telemetry/__init__.py +3 -0
  388. parrot/telemetry/mixin.py +111 -0
  389. parrot/template/__init__.py +3 -0
  390. parrot/template/engine.py +259 -0
  391. parrot/tools/__init__.py +23 -0
  392. parrot/tools/abstract.py +644 -0
  393. parrot/tools/agent.py +363 -0
  394. parrot/tools/arangodbsearch.py +537 -0
  395. parrot/tools/arxiv_tool.py +188 -0
  396. parrot/tools/calculator/__init__.py +3 -0
  397. parrot/tools/calculator/operations/__init__.py +38 -0
  398. parrot/tools/calculator/operations/calculus.py +80 -0
  399. parrot/tools/calculator/operations/statistics.py +76 -0
  400. parrot/tools/calculator/tool.py +150 -0
  401. parrot/tools/cloudwatch.py +988 -0
  402. parrot/tools/codeinterpreter/__init__.py +127 -0
  403. parrot/tools/codeinterpreter/executor.py +371 -0
  404. parrot/tools/codeinterpreter/internals.py +473 -0
  405. parrot/tools/codeinterpreter/models.py +643 -0
  406. parrot/tools/codeinterpreter/prompts.py +224 -0
  407. parrot/tools/codeinterpreter/tool.py +664 -0
  408. parrot/tools/company_info/__init__.py +6 -0
  409. parrot/tools/company_info/tool.py +1138 -0
  410. parrot/tools/correlationanalysis.py +437 -0
  411. parrot/tools/database/abstract.py +286 -0
  412. parrot/tools/database/bq.py +115 -0
  413. parrot/tools/database/cache.py +284 -0
  414. parrot/tools/database/models.py +95 -0
  415. parrot/tools/database/pg.py +343 -0
  416. parrot/tools/databasequery.py +1159 -0
  417. parrot/tools/db.py +1800 -0
  418. parrot/tools/ddgo.py +370 -0
  419. parrot/tools/decorators.py +271 -0
  420. parrot/tools/dftohtml.py +282 -0
  421. parrot/tools/document.py +549 -0
  422. parrot/tools/ecs.py +819 -0
  423. parrot/tools/edareport.py +368 -0
  424. parrot/tools/elasticsearch.py +1049 -0
  425. parrot/tools/employees.py +462 -0
  426. parrot/tools/epson/__init__.py +96 -0
  427. parrot/tools/excel.py +683 -0
  428. parrot/tools/file/__init__.py +13 -0
  429. parrot/tools/file/abstract.py +76 -0
  430. parrot/tools/file/gcs.py +378 -0
  431. parrot/tools/file/local.py +284 -0
  432. parrot/tools/file/s3.py +511 -0
  433. parrot/tools/file/tmp.py +309 -0
  434. parrot/tools/file/tool.py +501 -0
  435. parrot/tools/file_reader.py +129 -0
  436. parrot/tools/flowtask/__init__.py +19 -0
  437. parrot/tools/flowtask/tool.py +761 -0
  438. parrot/tools/gittoolkit.py +508 -0
  439. parrot/tools/google/__init__.py +18 -0
  440. parrot/tools/google/base.py +169 -0
  441. parrot/tools/google/tools.py +1251 -0
  442. parrot/tools/googlelocation.py +5 -0
  443. parrot/tools/googleroutes.py +5 -0
  444. parrot/tools/googlesearch.py +5 -0
  445. parrot/tools/googlesitesearch.py +5 -0
  446. parrot/tools/googlevoice.py +2 -0
  447. parrot/tools/gvoice.py +695 -0
  448. parrot/tools/ibisworld/README.md +225 -0
  449. parrot/tools/ibisworld/__init__.py +11 -0
  450. parrot/tools/ibisworld/tool.py +366 -0
  451. parrot/tools/jiratoolkit.py +1718 -0
  452. parrot/tools/manager.py +1098 -0
  453. parrot/tools/math.py +152 -0
  454. parrot/tools/metadata.py +476 -0
  455. parrot/tools/msteams.py +1621 -0
  456. parrot/tools/msword.py +635 -0
  457. parrot/tools/multidb.py +580 -0
  458. parrot/tools/multistoresearch.py +369 -0
  459. parrot/tools/networkninja.py +167 -0
  460. parrot/tools/nextstop/__init__.py +4 -0
  461. parrot/tools/nextstop/base.py +286 -0
  462. parrot/tools/nextstop/employee.py +733 -0
  463. parrot/tools/nextstop/store.py +462 -0
  464. parrot/tools/notification.py +435 -0
  465. parrot/tools/o365/__init__.py +42 -0
  466. parrot/tools/o365/base.py +295 -0
  467. parrot/tools/o365/bundle.py +522 -0
  468. parrot/tools/o365/events.py +554 -0
  469. parrot/tools/o365/mail.py +992 -0
  470. parrot/tools/o365/onedrive.py +497 -0
  471. parrot/tools/o365/sharepoint.py +641 -0
  472. parrot/tools/openapi_toolkit.py +904 -0
  473. parrot/tools/openweather.py +527 -0
  474. parrot/tools/pdfprint.py +1001 -0
  475. parrot/tools/powerbi.py +518 -0
  476. parrot/tools/powerpoint.py +1113 -0
  477. parrot/tools/pricestool.py +146 -0
  478. parrot/tools/products/__init__.py +246 -0
  479. parrot/tools/prophet_tool.py +171 -0
  480. parrot/tools/pythonpandas.py +630 -0
  481. parrot/tools/pythonrepl.py +910 -0
  482. parrot/tools/qsource.py +436 -0
  483. parrot/tools/querytoolkit.py +395 -0
  484. parrot/tools/quickeda.py +827 -0
  485. parrot/tools/resttool.py +553 -0
  486. parrot/tools/retail/__init__.py +0 -0
  487. parrot/tools/retail/bby.py +528 -0
  488. parrot/tools/sandboxtool.py +703 -0
  489. parrot/tools/sassie/__init__.py +352 -0
  490. parrot/tools/scraping/__init__.py +7 -0
  491. parrot/tools/scraping/docs/select.md +466 -0
  492. parrot/tools/scraping/documentation.md +1278 -0
  493. parrot/tools/scraping/driver.py +436 -0
  494. parrot/tools/scraping/models.py +576 -0
  495. parrot/tools/scraping/options.py +85 -0
  496. parrot/tools/scraping/orchestrator.py +517 -0
  497. parrot/tools/scraping/readme.md +740 -0
  498. parrot/tools/scraping/tool.py +3115 -0
  499. parrot/tools/seasonaldetection.py +642 -0
  500. parrot/tools/shell_tool/__init__.py +5 -0
  501. parrot/tools/shell_tool/actions.py +408 -0
  502. parrot/tools/shell_tool/engine.py +155 -0
  503. parrot/tools/shell_tool/models.py +322 -0
  504. parrot/tools/shell_tool/tool.py +442 -0
  505. parrot/tools/site_search.py +214 -0
  506. parrot/tools/textfile.py +418 -0
  507. parrot/tools/think.py +378 -0
  508. parrot/tools/toolkit.py +298 -0
  509. parrot/tools/webapp_tool.py +187 -0
  510. parrot/tools/whatif.py +1279 -0
  511. parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
  512. parrot/tools/workday/__init__.py +6 -0
  513. parrot/tools/workday/models.py +1389 -0
  514. parrot/tools/workday/tool.py +1293 -0
  515. parrot/tools/yfinance_tool.py +306 -0
  516. parrot/tools/zipcode.py +217 -0
  517. parrot/utils/__init__.py +2 -0
  518. parrot/utils/helpers.py +73 -0
  519. parrot/utils/parsers/__init__.py +5 -0
  520. parrot/utils/parsers/toml.c +12078 -0
  521. parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
  522. parrot/utils/parsers/toml.pyx +21 -0
  523. parrot/utils/toml.py +11 -0
  524. parrot/utils/types.cpp +20936 -0
  525. parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
  526. parrot/utils/types.pyx +213 -0
  527. parrot/utils/uv.py +11 -0
  528. parrot/version.py +10 -0
  529. parrot/yaml-rs/Cargo.lock +350 -0
  530. parrot/yaml-rs/Cargo.toml +19 -0
  531. parrot/yaml-rs/pyproject.toml +19 -0
  532. parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
  533. parrot/yaml-rs/src/lib.rs +222 -0
  534. requirements/docker-compose.yml +24 -0
  535. requirements/requirements-dev.txt +21 -0
@@ -0,0 +1,743 @@
1
+ """
2
+ Notification Mixin for AI-Parrot Agents.
3
+
4
+ Provides notification capabilities to agents using the async-notify library.
5
+ """
6
+ from typing import Union, List, Optional, Dict, Any
7
+ from pathlib import Path
8
+ from dataclasses import dataclass
9
+ from enum import Enum
10
+ import mimetypes
11
+ from notify import Notify
12
+ from notify.models import (
13
+ Actor,
14
+ Channel,
15
+ Chat,
16
+ TeamsChannel,
17
+ TeamsWebhook,
18
+ TeamsCard
19
+ )
20
+ from notify.providers.email import Email
21
+ from notify.providers.slack import Slack
22
+ from notify.providers.telegram import Telegram
23
+ from notify.providers.teams import Teams
24
+ from ..conf import (
25
+ TEAMS_NOTIFY_TENANT_ID,
26
+ TEAMS_NOTIFY_CLIENT_ID,
27
+ TEAMS_NOTIFY_CLIENT_SECRET,
28
+ TEAMS_NOTIFY_USERNAME,
29
+ TEAMS_NOTIFY_PASSWORD
30
+ )
31
+
32
+
33
+ class NotificationProvider(Enum):
34
+ """Supported notification providers."""
35
+ EMAIL = "email"
36
+ SLACK = "slack"
37
+ TELEGRAM = "telegram"
38
+ TEAMS = "teams"
39
+
40
+
41
+ class FileType(Enum):
42
+ """File types for smart handling."""
43
+ IMAGE = "image"
44
+ DOCUMENT = "document"
45
+ VIDEO = "video"
46
+ AUDIO = "audio"
47
+ UNKNOWN = "unknown"
48
+
49
+
50
+ @dataclass
51
+ class NotificationConfig:
52
+ """Configuration for sending notifications."""
53
+ provider: NotificationProvider = NotificationProvider.EMAIL
54
+ recipients: Union[List[Actor], Actor, Channel, Chat] = None
55
+ subject: Optional[str] = None
56
+ template: Optional[str] = None
57
+ disable_notification: bool = False
58
+ # Email specific
59
+ with_attachments: bool = True
60
+ # Teams specific
61
+ teams_config: Optional[Dict[str, Any]] = None
62
+ # Additional provider kwargs
63
+ provider_kwargs: Optional[Dict[str, Any]] = None
64
+
65
+
66
+ class NotificationMixin:
67
+ """
68
+ Mixin to provide notification capabilities to agents.
69
+
70
+ This mixin integrates async-notify library to send messages
71
+ through various channels (email, slack, telegram, teams) with
72
+ smart file handling.
73
+ """
74
+
75
+ def _classify_file(self, file_path: Path) -> FileType:
76
+ """
77
+ Classify file type based on extension and MIME type.
78
+
79
+ Args:
80
+ file_path: Path to the file
81
+
82
+ Returns:
83
+ FileType enum value
84
+ """
85
+ if not file_path.exists():
86
+ return FileType.UNKNOWN
87
+
88
+ # Get MIME type
89
+ mime_type, _ = mimetypes.guess_type(str(file_path))
90
+
91
+ if mime_type:
92
+ if mime_type.startswith('image/'):
93
+ return FileType.IMAGE
94
+ elif mime_type.startswith('video/'):
95
+ return FileType.VIDEO
96
+ elif mime_type.startswith('audio/'):
97
+ return FileType.AUDIO
98
+
99
+ # Check by extension if MIME failed
100
+ ext = file_path.suffix.lower()
101
+
102
+ image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'}
103
+ video_extensions = {'.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv'}
104
+ audio_extensions = {'.mp3', '.wav', '.ogg', '.m4a', '.flac', '.aac'}
105
+
106
+ if ext in image_extensions:
107
+ return FileType.IMAGE
108
+ elif ext in video_extensions:
109
+ return FileType.VIDEO
110
+ elif ext in audio_extensions:
111
+ return FileType.AUDIO
112
+ else:
113
+ return FileType.DOCUMENT
114
+
115
+ def _categorize_files(self, files: List[Path]) -> Dict[FileType, List[Path]]:
116
+ """
117
+ Categorize files by type.
118
+
119
+ Args:
120
+ files: List of file paths
121
+
122
+ Returns:
123
+ Dictionary mapping FileType to list of files
124
+ """
125
+ categorized = {
126
+ FileType.IMAGE: [],
127
+ FileType.DOCUMENT: [],
128
+ FileType.VIDEO: [],
129
+ FileType.AUDIO: []
130
+ }
131
+
132
+ for file in files:
133
+ file_type = self._classify_file(file)
134
+ if file_type != FileType.UNKNOWN:
135
+ categorized[file_type].append(file)
136
+
137
+ return categorized
138
+
139
+ async def send_notification(
140
+ self,
141
+ message: Union[str, Any],
142
+ recipients: Union[List[Actor], Actor, Channel, Chat, str, List[str]],
143
+ provider: Union[str, NotificationProvider] = NotificationProvider.EMAIL,
144
+ subject: Optional[str] = None,
145
+ report: Optional[Any] = None,
146
+ template: Optional[str] = None,
147
+ with_attachments: bool = True,
148
+ **kwargs
149
+ ) -> Dict[str, Any]:
150
+ """
151
+ Send notification to users through various channels.
152
+
153
+ Args:
154
+ message: Message text to send or AgentResponse/AIMessage object
155
+ recipients: Recipients (can be Actor objects, email strings, etc.)
156
+ provider: Notification provider (email, slack, telegram, teams)
157
+ subject: Subject line (mainly for email)
158
+ report: Optional AgentResponse or AIMessage containing output/files
159
+ template: Email template name
160
+ with_attachments: Whether to include file attachments
161
+ **kwargs: Additional provider-specific arguments
162
+
163
+ Returns:
164
+ Dict with notification status and results
165
+
166
+ Example:
167
+ # Simple email notification
168
+ await agent.send_notification(
169
+ message="Daily report ready",
170
+ recipients="user@example.com",
171
+ subject="Daily Report"
172
+ )
173
+
174
+ # With AIMessage containing files (images sent as photos in Telegram)
175
+ response = await agent.chat("Generate report with charts")
176
+ await agent.send_notification(
177
+ message="Your report is ready",
178
+ recipients="123456789", # Telegram chat_id
179
+ report=response,
180
+ provider="telegram"
181
+ )
182
+
183
+ # To Slack channel
184
+ channel = Channel(channel_id="C123456", channel_name="reports")
185
+ await agent.send_notification(
186
+ message="New insights available",
187
+ recipients=channel,
188
+ provider="slack"
189
+ )
190
+ """
191
+ try:
192
+ # Normalize provider
193
+ if isinstance(provider, str):
194
+ provider = NotificationProvider(provider.lower())
195
+
196
+ # Extract message content from AgentResponse/AIMessage if needed
197
+ message_text, files = self._extract_message_content(message, report)
198
+
199
+ # Parse recipients
200
+ recipient_list = self._parse_recipients(recipients, provider)
201
+
202
+ # Prepare notification arguments
203
+ notify_args = {
204
+ "message": message_text,
205
+ "recipient": recipient_list
206
+ }
207
+
208
+ # Add provider-specific arguments
209
+ if provider == NotificationProvider.EMAIL:
210
+ if subject:
211
+ notify_args["subject"] = subject
212
+ if template:
213
+ notify_args["template"] = template
214
+ if files and with_attachments:
215
+ notify_args["attachments"] = files
216
+
217
+ elif provider == NotificationProvider.TELEGRAM:
218
+ notify_args["disable_notification"] = kwargs.get(
219
+ "disable_notification", False
220
+ )
221
+
222
+ elif provider == NotificationProvider.TEAMS:
223
+ # Teams might need special card formatting
224
+ pass
225
+
226
+ # Merge additional kwargs
227
+ notify_args |= kwargs
228
+
229
+ # Send notification with smart file handling
230
+ result = await self._send_with_provider(
231
+ provider=provider,
232
+ notify_args=notify_args,
233
+ files=files if with_attachments else None
234
+ )
235
+
236
+ self.logger.info(
237
+ f"Notification sent via {provider.value} to {len(recipient_list) if isinstance(recipient_list, list) else 1} recipient(s)"
238
+ )
239
+
240
+ return {
241
+ "status": "success",
242
+ "provider": provider.value,
243
+ "recipients": len(recipient_list) if isinstance(recipient_list, list) else 1,
244
+ "files_sent": len(files) if files else 0,
245
+ "result": result
246
+ }
247
+
248
+ except Exception as e:
249
+ self.logger.error(f"Failed to send notification: {e}", exc_info=True)
250
+ return {
251
+ "status": "error",
252
+ "error": str(e),
253
+ "provider": provider.value if provider else None
254
+ }
255
+
256
+ def _extract_message_content(
257
+ self,
258
+ message: Union[str, Any],
259
+ report: Optional[Any] = None
260
+ ) -> tuple[str, List[Path]]:
261
+ """
262
+ Extract message text and files from various input types.
263
+
264
+ Handles:
265
+ - Plain strings
266
+ - AgentResponse objects
267
+ - AIMessage objects
268
+
269
+ Returns:
270
+ Tuple of (message_text, list_of_file_paths)
271
+ """
272
+ message_text = ""
273
+ files = []
274
+
275
+ # Extract from message if it's an object
276
+ if isinstance(message, str):
277
+ message_text = message
278
+ elif hasattr(message, 'content'):
279
+ # AIMessage or similar
280
+ message_text = getattr(message, 'content', str(message))
281
+ elif hasattr(message, 'output'):
282
+ # AgentResponse
283
+ message_text = getattr(message, 'output', str(message))
284
+ else:
285
+ message_text = str(message)
286
+
287
+ # Extract from report if provided
288
+ if report:
289
+ if hasattr(report, 'output'):
290
+ # AgentResponse with output
291
+ output = getattr(report, 'output', None)
292
+ if output and not message_text:
293
+ message_text = output
294
+
295
+ # PRIORITY 1: Check for 'files' attribute first (preferred for AgentResponse)
296
+ if hasattr(report, 'files'):
297
+ report_files = getattr(report, 'files', None)
298
+ if report_files:
299
+ if isinstance(report_files, list):
300
+ for file_path in report_files:
301
+ if file_path and self._is_valid_file_path(file_path):
302
+ files.append(Path(file_path))
303
+ elif isinstance(report_files, (str, Path)):
304
+ if self._is_valid_file_path(report_files):
305
+ files.append(Path(report_files))
306
+
307
+ # PRIORITY 2: Check for 'documents' attribute (but filter out text content)
308
+ elif hasattr(report, 'documents'):
309
+ documents = getattr(report, 'documents', None)
310
+ if documents:
311
+ if isinstance(documents, list):
312
+ for document in documents:
313
+ if isinstance(document, dict) and 'path' in document:
314
+ doc_path = document['path']
315
+ if self._is_valid_file_path(doc_path):
316
+ files.append(Path(doc_path))
317
+ elif isinstance(document, (str, Path)):
318
+ # Only add if it looks like a file path, not text content
319
+ if self._is_valid_file_path(document):
320
+ files.append(Path(document))
321
+
322
+ # Check for content blocks with files (Claude-style responses)
323
+ if hasattr(report, 'content') and isinstance(report.content, list):
324
+ for block in report.content:
325
+ if isinstance(block, dict):
326
+ if block.get('type') == 'file' and 'path' in block:
327
+ file_path = block['path']
328
+ if self._is_valid_file_path(file_path):
329
+ files.append(Path(file_path))
330
+ elif block.get('type') == 'image' and 'source' in block:
331
+ # Handle image sources
332
+ source = block['source']
333
+ if isinstance(source, dict) and 'data' in source:
334
+ # Base64 image - would need to save first
335
+ pass
336
+ elif isinstance(source, (str, Path)):
337
+ if self._is_valid_file_path(source):
338
+ files.append(Path(source))
339
+
340
+ # PRIORITY 3: Check AIMessage nested inside AgentResponse
341
+ if hasattr(report, 'response') and isinstance(report.response, object):
342
+ ai_message = report.response
343
+
344
+ # Extract files from AIMessage
345
+ if hasattr(ai_message, 'files'):
346
+ ai_files = getattr(ai_message, 'files', None)
347
+ if ai_files and isinstance(ai_files, list):
348
+ for file_path in ai_files:
349
+ if file_path and self._is_valid_file_path(file_path):
350
+ path_obj = Path(file_path)
351
+ if path_obj not in files: # Avoid duplicates
352
+ files.append(path_obj)
353
+
354
+ # Remove duplicates while preserving order
355
+ seen = set()
356
+ unique_files = []
357
+ for file in files:
358
+ if file not in seen:
359
+ seen.add(file)
360
+ unique_files.append(file)
361
+
362
+ files = unique_files
363
+
364
+ # Validate files exist
365
+ valid_files = [f for f in files if f.exists()]
366
+ if len(valid_files) < len(files):
367
+ missing_count = len(files) - len(valid_files)
368
+ missing_files = [str(f) for f in files if not f.exists()]
369
+ self.logger.warning(
370
+ f"Some files not found: {missing_count} missing - {missing_files[:3]}"
371
+ )
372
+
373
+ return message_text, valid_files
374
+
375
+ def _is_valid_file_path(self, path: Union[str, Path]) -> bool:
376
+ """
377
+ Check if a string looks like a valid file path rather than text content.
378
+
379
+ Args:
380
+ path: String or Path to validate
381
+
382
+ Returns:
383
+ bool: True if it looks like a file path, False otherwise
384
+ """
385
+ if not path:
386
+ return False
387
+
388
+ path_str = str(path)
389
+
390
+ # Filter out obvious text content (too long or contains newlines)
391
+ if len(path_str) > 500 or '\n' in path_str:
392
+ return False
393
+
394
+ # Must contain path separators or start with common path patterns
395
+ has_separator = '/' in path_str or '\\' in path_str
396
+
397
+ # Check for common file extensions
398
+ common_extensions = [
399
+ '.txt', '.pdf', '.doc', '.docx', '.xls', '.xlsx',
400
+ '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.svg',
401
+ '.mp4', '.avi', '.mov', '.mp3', '.wav',
402
+ '.json', '.xml', '.csv', '.html', '.md'
403
+ ]
404
+ has_extension = any(path_str.lower().endswith(ext) for ext in common_extensions)
405
+
406
+ # Valid if it has separators or a file extension
407
+ return has_separator or has_extension
408
+
409
+ def _parse_recipients(
410
+ self,
411
+ recipients: Union[List[Actor], Actor, Channel, Chat, str, List[str]],
412
+ provider: NotificationProvider
413
+ ) -> Union[List[Actor], Actor, Channel, Chat]:
414
+ """
415
+ Parse recipients into appropriate format for the provider.
416
+
417
+ Args:
418
+ recipients: Various recipient formats
419
+ provider: The notification provider
420
+
421
+ Returns:
422
+ Formatted recipient(s) for the provider
423
+ """
424
+ # Already formatted objects
425
+ if isinstance(recipients, (Actor, Channel, Chat, TeamsChannel, TeamsWebhook)):
426
+ return recipients
427
+
428
+ # List of formatted objects
429
+ if isinstance(recipients, list) and len(recipients) > 0:
430
+ if isinstance(recipients[0], (Actor, Channel, Chat)):
431
+ return recipients
432
+
433
+ # String email address(es)
434
+ if isinstance(recipients, str):
435
+ # Single email
436
+ if '@' in recipients:
437
+ return Actor(
438
+ name=recipients.split('@')[0],
439
+ account={"address": recipients}
440
+ )
441
+ # Might be a chat_id or channel_id
442
+ elif provider == NotificationProvider.TELEGRAM:
443
+ return Chat(chat_id=recipients)
444
+ elif provider == NotificationProvider.SLACK:
445
+ return Channel(channel_id=recipients)
446
+ else:
447
+ # Try as generic actor
448
+ return Actor(
449
+ name="User",
450
+ account={"address": recipients}
451
+ )
452
+
453
+ # List of email strings
454
+ if isinstance(recipients, list):
455
+ actors = []
456
+ for recipient in recipients:
457
+ if isinstance(recipient, str) and '@' in recipient:
458
+ actors.append(Actor(
459
+ name=recipient.split('@')[0],
460
+ account={"address": recipient}
461
+ ))
462
+ elif isinstance(recipient, (Actor, Channel, Chat)):
463
+ actors.append(recipient)
464
+ return actors if actors else recipients
465
+
466
+ # Fallback
467
+ return recipients
468
+
469
+ async def _send_with_provider(
470
+ self,
471
+ provider: NotificationProvider,
472
+ notify_args: Dict[str, Any],
473
+ files: Optional[List[Path]] = None
474
+ ) -> Any:
475
+ """
476
+ Send notification using the specified provider with smart file handling.
477
+
478
+ Args:
479
+ provider: Notification provider to use
480
+ notify_args: Arguments for the notification
481
+ files: Optional list of file attachments
482
+
483
+ Returns:
484
+ Provider-specific result
485
+ """
486
+ if provider == NotificationProvider.EMAIL:
487
+ return await self._send_email(notify_args, files)
488
+
489
+ elif provider == NotificationProvider.SLACK:
490
+ return await self._send_slack(notify_args)
491
+
492
+ elif provider == NotificationProvider.TELEGRAM:
493
+ return await self._send_telegram(notify_args, files)
494
+
495
+ elif provider == NotificationProvider.TEAMS:
496
+ return await self._send_teams(notify_args, files)
497
+
498
+ else:
499
+ raise ValueError(f"Unsupported provider: {provider}")
500
+
501
+ async def _send_email(
502
+ self,
503
+ notify_args: Dict[str, Any],
504
+ files: Optional[List[Path]] = None
505
+ ) -> Any:
506
+ """Send email notification with attachments."""
507
+ email = Email()
508
+ async with email as conn:
509
+ result = await conn.send(**notify_args)
510
+ return result
511
+
512
+ async def _send_slack(self, notify_args: Dict[str, Any]) -> Any:
513
+ """Send Slack notification."""
514
+ slack = Slack()
515
+ async with slack as conn:
516
+ result = await conn.send(**notify_args)
517
+ return result
518
+
519
+ async def _send_telegram(
520
+ self,
521
+ notify_args: Dict[str, Any],
522
+ files: Optional[List[Path]] = None
523
+ ) -> Any:
524
+ """
525
+ Send Telegram notification with smart file handling.
526
+
527
+ Images are sent as photos, documents as documents, videos as videos.
528
+ """
529
+ telegram = Telegram()
530
+ results = []
531
+
532
+ async with telegram as conn:
533
+ # If files, send them appropriately based on type
534
+ if files and len(files) > 0:
535
+ # Categorize files
536
+ categorized_files = self._categorize_files(files)
537
+
538
+ # Send images as photos
539
+ for image in categorized_files[FileType.IMAGE]:
540
+ try:
541
+ result = await conn.send_photo(
542
+ photo=image,
543
+ caption=notify_args.get("message", "")[:1024], # Telegram caption limit
544
+ disable_notification=notify_args.get("disable_notification", False)
545
+ )
546
+ results.append({
547
+ "type": "photo",
548
+ "file": str(image),
549
+ "result": result
550
+ })
551
+ self.logger.info(f"Sent image via Telegram: {image.name}")
552
+ except Exception as e:
553
+ self.logger.error(f"Failed to send image {image}: {e}")
554
+
555
+ # Send videos
556
+ for video in categorized_files[FileType.VIDEO]:
557
+ try:
558
+ result = await conn.send_video(
559
+ video=video,
560
+ caption=notify_args.get("message", "")[:1024],
561
+ supports_streaming=True,
562
+ disable_notification=notify_args.get("disable_notification", False)
563
+ )
564
+ results.append({
565
+ "type": "video",
566
+ "file": str(video),
567
+ "result": result
568
+ })
569
+ self.logger.info(f"Sent video via Telegram: {video.name}")
570
+ except Exception as e:
571
+ self.logger.error(f"Failed to send video {video}: {e}")
572
+
573
+ # Send audio files
574
+ for audio in categorized_files[FileType.AUDIO]:
575
+ try:
576
+ result = await conn.send_audio(
577
+ audio=audio,
578
+ caption=notify_args.get("message", "")[:1024],
579
+ disable_notification=notify_args.get("disable_notification", False)
580
+ )
581
+ results.append({
582
+ "type": "audio",
583
+ "file": str(audio),
584
+ "result": result
585
+ })
586
+ self.logger.info(f"Sent audio via Telegram: {audio.name}")
587
+ except Exception as e:
588
+ self.logger.error(f"Failed to send audio {audio}: {e}")
589
+
590
+ # Send documents (PDFs, Excel, etc.)
591
+ for document in categorized_files[FileType.DOCUMENT]:
592
+ try:
593
+ result = await conn.send_document(
594
+ document=document,
595
+ caption=notify_args.get("message", "")[:1024],
596
+ disable_notification=notify_args.get("disable_notification", False)
597
+ )
598
+ results.append({
599
+ "type": "document",
600
+ "file": str(document),
601
+ "result": result
602
+ })
603
+ self.logger.info(f"Sent document via Telegram: {document.name}")
604
+ except Exception as e:
605
+ self.logger.error(f"Failed to send document {document}: {e}")
606
+
607
+ # If message was used as caption, we're done
608
+ # Otherwise send a separate text message
609
+ if len(results) > 0 and notify_args.get("message"):
610
+ # Message already sent as captions
611
+ pass
612
+ else:
613
+ # Send text message
614
+ text_result = await conn.send(**notify_args)
615
+ results.append({
616
+ "type": "text",
617
+ "result": text_result
618
+ })
619
+ else:
620
+ # No files, just send text message
621
+ result = await conn.send(**notify_args)
622
+ results.append({
623
+ "type": "text",
624
+ "result": result
625
+ })
626
+
627
+ return results
628
+
629
+ async def _send_teams(
630
+ self,
631
+ notify_args: Dict[str, Any],
632
+ files: Optional[List[Path]] = None
633
+ ) -> Any:
634
+ """
635
+ Send Microsoft Teams notification.
636
+
637
+ Teams supports file attachments in cards.
638
+ """
639
+ teams = Teams(
640
+ as_user=True,
641
+ tenant_id=TEAMS_NOTIFY_TENANT_ID,
642
+ client_id=TEAMS_NOTIFY_CLIENT_ID,
643
+ client_secret=TEAMS_NOTIFY_CLIENT_SECRET,
644
+ username=TEAMS_NOTIFY_USERNAME,
645
+ password=TEAMS_NOTIFY_PASSWORD
646
+ )
647
+
648
+ # If files provided, we can add them as attachments or links in the card
649
+ if files and len(files) > 0:
650
+ # Create a Teams card with file information
651
+ message_text = notify_args.get("message", "")
652
+
653
+ # Add file list to message
654
+ file_list = "\n".join([f"- {f.name}" for f in files])
655
+ enhanced_message = f"{message_text}\n\n**Attached Files:**\n{file_list}"
656
+
657
+ notify_args["message"] = enhanced_message
658
+
659
+ # Note: Teams API has limitations on direct file uploads
660
+ # For full file upload support, would need to use Graph API file upload
661
+ self.logger.info(
662
+ f"Teams notification with {len(files)} files (file list added to message)"
663
+ )
664
+
665
+ async with teams as conn:
666
+ result = await conn.send(**notify_args)
667
+ return result
668
+
669
+ # Convenience methods for specific providers
670
+
671
+ async def send_email(
672
+ self,
673
+ message: str,
674
+ recipients: Union[List[str], str],
675
+ subject: str,
676
+ report: Optional[Any] = None,
677
+ template: Optional[str] = None,
678
+ **kwargs
679
+ ) -> Dict[str, Any]:
680
+ """Convenience method for sending emails."""
681
+ return await self.send_notification(
682
+ message=message,
683
+ recipients=recipients,
684
+ provider=NotificationProvider.EMAIL,
685
+ subject=subject,
686
+ report=report,
687
+ template=template,
688
+ **kwargs
689
+ )
690
+
691
+ async def send_slack_message(
692
+ self,
693
+ message: str,
694
+ channel: Union[Channel, str],
695
+ report: Optional[Any] = None,
696
+ **kwargs
697
+ ) -> Dict[str, Any]:
698
+ """Convenience method for sending Slack messages."""
699
+ return await self.send_notification(
700
+ message=message,
701
+ recipients=channel,
702
+ provider=NotificationProvider.SLACK,
703
+ report=report,
704
+ **kwargs
705
+ )
706
+
707
+ async def send_telegram_message(
708
+ self,
709
+ message: str,
710
+ chat: Union[Chat, str],
711
+ report: Optional[Any] = None,
712
+ disable_notification: bool = False,
713
+ **kwargs
714
+ ) -> Dict[str, Any]:
715
+ """
716
+ Convenience method for sending Telegram messages.
717
+
718
+ Automatically detects and sends images as photos, documents as files.
719
+ """
720
+ return await self.send_notification(
721
+ message=message,
722
+ recipients=chat,
723
+ provider=NotificationProvider.TELEGRAM,
724
+ report=report,
725
+ disable_notification=disable_notification,
726
+ **kwargs
727
+ )
728
+
729
+ async def send_teams_message(
730
+ self,
731
+ message: str,
732
+ recipient: Union[Actor, TeamsChannel, TeamsWebhook],
733
+ report: Optional[Any] = None,
734
+ **kwargs
735
+ ) -> Dict[str, Any]:
736
+ """Convenience method for sending Teams messages."""
737
+ return await self.send_notification(
738
+ message=message,
739
+ recipients=recipient,
740
+ provider=NotificationProvider.TEAMS,
741
+ report=report,
742
+ **kwargs
743
+ )