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,992 @@
1
+ """
2
+ Office365 Mails Tools.
3
+
4
+ Specific tools for interacting with Office365 services:
5
+ - CreateDraftMessage: Create email drafts
6
+ - SearchEmail: Search through emails
7
+ - SendEmail: Send emails directly
8
+ """
9
+ from typing import Dict, Any, Optional, List, Type
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ import base64
13
+ from pydantic import BaseModel, Field
14
+ import aiofiles
15
+ from msgraph.generated.models.message import Message
16
+ from msgraph.generated.models.recipient import Recipient
17
+ from msgraph.generated.models.email_address import EmailAddress
18
+ from msgraph.generated.models.item_body import ItemBody
19
+ from msgraph.generated.models.body_type import BodyType
20
+ from msgraph.generated.models.importance import Importance
21
+ from msgraph.generated.users.item.mail_folders.item.messages.messages_request_builder import (
22
+ MessagesRequestBuilder
23
+ )
24
+ from msgraph.generated.users.item.send_mail.send_mail_post_request_body import (
25
+ SendMailPostRequestBody
26
+ )
27
+ from kiota_abstractions.base_request_configuration import RequestConfiguration
28
+
29
+ from .base import O365Tool, O365ToolArgsSchema, O365Client
30
+
31
+
32
+ # ============================================================================
33
+ # CREATE DRAFT MESSAGE TOOL
34
+ # ============================================================================
35
+
36
+ class CreateDraftMessageArgs(O365ToolArgsSchema):
37
+ """Arguments for creating a draft email message."""
38
+ subject: str = Field(
39
+ description="Email subject line"
40
+ )
41
+ body: str = Field(
42
+ description="Email body content (can be HTML or plain text)"
43
+ )
44
+ to_recipients: List[str] = Field(
45
+ description="List of recipient email addresses"
46
+ )
47
+ cc_recipients: Optional[List[str]] = Field(
48
+ default=None,
49
+ description="List of CC recipient email addresses"
50
+ )
51
+ bcc_recipients: Optional[List[str]] = Field(
52
+ default=None,
53
+ description="List of BCC recipient email addresses"
54
+ )
55
+ importance: Optional[str] = Field(
56
+ default="normal",
57
+ description="Email importance: 'low', 'normal', or 'high'"
58
+ )
59
+ is_html: bool = Field(
60
+ default=False,
61
+ description="Whether the body is HTML (True) or plain text (False)"
62
+ )
63
+
64
+ class CreateDraftMessageTool(O365Tool):
65
+ """
66
+ Tool for creating draft email messages in Office365.
67
+
68
+ This tool creates a draft email message that can be reviewed and sent later.
69
+ The draft is saved in the user's Drafts folder.
70
+
71
+ Examples:
72
+ # Create a simple draft
73
+ result = await tool.run(
74
+ subject="Project Update",
75
+ body="Here's the latest update on the project...",
76
+ to_recipients=["colleague@company.com"]
77
+ )
78
+
79
+ # Create an HTML draft with CC
80
+ result = await tool.run(
81
+ subject="Monthly Report",
82
+ body="<h1>Report</h1><p>Details here...</p>",
83
+ to_recipients=["boss@company.com"],
84
+ cc_recipients=["team@company.com"],
85
+ importance="high",
86
+ is_html=True
87
+ )
88
+ """
89
+
90
+ name: str = "create_draft_message"
91
+ description: str = (
92
+ "Create a draft email message in Office365. "
93
+ "The draft is saved in the Drafts folder and can be sent later."
94
+ )
95
+ args_schema: Type[BaseModel] = CreateDraftMessageArgs
96
+
97
+ async def _execute_graph_operation(
98
+ self,
99
+ client: O365Client,
100
+ **kwargs
101
+ ) -> Dict[str, Any]:
102
+ """
103
+ Create a draft email using Microsoft Graph API.
104
+
105
+ Args:
106
+ client: Authenticated O365Client
107
+ **kwargs: Draft parameters
108
+
109
+ Returns:
110
+ Dict with draft details
111
+ """
112
+ # Extract parameters
113
+ subject = kwargs.get('subject')
114
+ body_content = kwargs.get('body')
115
+ to_recipients = kwargs.get('to_recipients', [])
116
+ cc_recipients = kwargs.get('cc_recipients')
117
+ bcc_recipients = kwargs.get('bcc_recipients')
118
+ importance_str = kwargs.get('importance', 'normal')
119
+ is_html = kwargs.get('is_html', False)
120
+ user_id = kwargs.get('user_id')
121
+
122
+ try:
123
+ # Get user context
124
+ mailbox = client.get_user_context(user_id=user_id)
125
+
126
+ # Build message object
127
+ message = Message()
128
+ message.subject = subject
129
+
130
+ # Set body
131
+ message.body = ItemBody()
132
+ message.body.content = body_content
133
+ message.body.content_type = BodyType.Html if is_html else BodyType.Text
134
+
135
+ # Helper function to create recipient
136
+ def create_recipient(email: str) -> Recipient:
137
+ recipient = Recipient()
138
+ recipient.email_address = EmailAddress()
139
+ recipient.email_address.address = email
140
+ return recipient
141
+
142
+ # Set recipients
143
+ message.to_recipients = [create_recipient(email) for email in to_recipients]
144
+
145
+ if cc_recipients:
146
+ message.cc_recipients = [create_recipient(email) for email in cc_recipients]
147
+
148
+ if bcc_recipients:
149
+ message.bcc_recipients = [create_recipient(email) for email in bcc_recipients]
150
+
151
+ # Set importance
152
+ importance_map = {
153
+ 'low': Importance.Low,
154
+ 'normal': Importance.Normal,
155
+ 'high': Importance.High
156
+ }
157
+ message.importance = importance_map.get(importance_str.lower(), Importance.Normal)
158
+
159
+ # Create the draft
160
+ self.logger.info(f"Creating draft message: {subject}")
161
+ draft = await mailbox.messages.post(message)
162
+
163
+ self.logger.info(f"Created draft with ID: {draft.id}")
164
+
165
+ return {
166
+ "status": "created",
167
+ "id": draft.id,
168
+ "subject": draft.subject,
169
+ "to_recipients": to_recipients,
170
+ "cc_recipients": cc_recipients or [],
171
+ "bcc_recipients": bcc_recipients or [],
172
+ "importance": importance_str,
173
+ "is_html": is_html,
174
+ "web_link": draft.web_link,
175
+ "created_datetime": draft.created_date_time.isoformat() if draft.created_date_time else None
176
+ }
177
+
178
+ except Exception as e:
179
+ self.logger.error(f"Failed to create draft message: {e}", exc_info=True)
180
+ raise
181
+
182
+ # ============================================================================
183
+ # SEARCH EMAIL TOOL
184
+ # ============================================================================
185
+
186
+ class SearchEmailArgs(O365ToolArgsSchema):
187
+ """Arguments for searching emails."""
188
+ query: str = Field(
189
+ description="Search query string (supports keywords, from:, to:, subject:, etc.)"
190
+ )
191
+ folder: Optional[str] = Field(
192
+ default="inbox",
193
+ description="Folder to search in: 'inbox', 'sentitems', 'drafts', 'deleteditems', or folder ID"
194
+ )
195
+ max_results: int = Field(
196
+ default=10,
197
+ description="Maximum number of results to return (1-50)"
198
+ )
199
+ include_attachments: bool = Field(
200
+ default=False,
201
+ description="Whether to include attachment information in results"
202
+ )
203
+ order_by: str = Field(
204
+ default="receivedDateTime desc",
205
+ description=(
206
+ "Sort order (e.g., 'receivedDateTime desc', 'subject asc'). "
207
+ "Note: When using search queries, sorting is done client-side after retrieval."
208
+ )
209
+ )
210
+
211
+ class SearchEmailTool(O365Tool):
212
+ """
213
+ Tool for searching emails in Office365.
214
+
215
+ This tool searches through emails with support for:
216
+ - Advanced search queries
217
+ - Folder-specific searches
218
+ - Sorting and limiting results
219
+ - Attachment information
220
+
221
+ Search query examples:
222
+ - "project update" - Keywords in subject or body
223
+ - "from:john@company.com" - Emails from specific sender
224
+ - "subject:invoice" - Search in subject only
225
+ - "hasAttachments:true" - Only emails with attachments
226
+ - "received>=2025-01-01" - Emails received after date
227
+
228
+ Examples:
229
+ # Search for recent emails
230
+ result = await tool.run(
231
+ query="project deadline",
232
+ max_results=5
233
+ )
234
+
235
+ # Search sent items
236
+ result = await tool.run(
237
+ query="from:me to:client@company.com",
238
+ folder="sentitems",
239
+ max_results=10
240
+ )
241
+
242
+ # Search with attachments
243
+ result = await tool.run(
244
+ query="invoice hasAttachments:true",
245
+ include_attachments=True
246
+ )
247
+ """
248
+
249
+ name: str = "search_email"
250
+ description: str = (
251
+ "Search through emails in Office365. "
252
+ "Supports advanced queries, folder filtering, and sorting."
253
+ )
254
+ args_schema: Type[BaseModel] = SearchEmailArgs
255
+
256
+ async def _execute_graph_operation(
257
+ self,
258
+ client: O365Client,
259
+ **kwargs
260
+ ) -> Dict[str, Any]:
261
+ """
262
+ Search emails using Microsoft Graph API.
263
+
264
+ Args:
265
+ client: Authenticated O365Client
266
+ **kwargs: Search parameters
267
+
268
+ Returns:
269
+ Dict with search results
270
+ """
271
+ query = kwargs.get('query')
272
+ folder = kwargs.get('folder', 'inbox')
273
+ max_results = min(kwargs.get('max_results', 10), 50) # Cap at 50
274
+ include_attachments = kwargs.get('include_attachments', False)
275
+ order_by = kwargs.get('order_by', 'receivedDateTime desc')
276
+ user_id = kwargs.get('user_id')
277
+
278
+ try:
279
+ # Build the request
280
+ mailbox = client.get_user_context(user_id=user_id)
281
+
282
+ # Select fields to return
283
+ select_fields = [
284
+ 'id', 'subject', 'from', 'toRecipients', 'receivedDateTime',
285
+ 'bodyPreview', 'isRead', 'hasAttachments', 'importance',
286
+ 'conversationId', 'webLink'
287
+ ]
288
+
289
+ if include_attachments:
290
+ select_fields.append('attachments')
291
+
292
+ # Apply folder filter and get appropriate request builder
293
+ folder_map = {
294
+ 'inbox': 'inbox',
295
+ 'sentitems': 'sentitems',
296
+ 'drafts': 'drafts',
297
+ 'deleteditems': 'deleteditems'
298
+ }
299
+
300
+ if folder.lower() in folder_map:
301
+ folder_name = folder_map[folder.lower()]
302
+ request_builder = mailbox.mail_folders.by_mail_folder_id(folder_name).messages
303
+
304
+ else:
305
+ # Direct messages (inbox shortcut)
306
+ request_builder = mailbox.messages
307
+
308
+ query_params_obj = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
309
+ top=max_results,
310
+ select=select_fields
311
+ )
312
+
313
+ # CRITICAL: Only add orderby if NOT using search
314
+ if not query:
315
+ query_params_obj.orderby = [order_by]
316
+
317
+ # Add search filter if query provided
318
+ if query:
319
+ query_params_obj.search = f'"{query}"'
320
+ self.logger.info(f"Using search (orderby disabled): '{query}'")
321
+ else:
322
+ self.logger.info(f"Listing messages with orderby: {order_by}")
323
+
324
+ # Wrap in RequestConfiguration
325
+ request_config = RequestConfiguration(
326
+ query_parameters=query_params_obj
327
+ )
328
+
329
+ # Execute search
330
+ self.logger.info(f"Searching emails: query='{query}', folder='{folder}'")
331
+ messages = await request_builder.get(request_configuration=request_config)
332
+
333
+ # Format results
334
+ results = []
335
+ if messages and messages.value:
336
+ for msg in messages.value:
337
+ result_item = {
338
+ "id": msg.id,
339
+ "subject": msg.subject or "(No subject)",
340
+ # Fix: Use from_ instead of from_prop
341
+ "from": msg.from_.email_address.address if msg.from_ and msg.from_.email_address else None,
342
+ "from_name": msg.from_.email_address.name if msg.from_ and msg.from_.email_address else None,
343
+ "to": [
344
+ r.email_address.address
345
+ for r in (msg.to_recipients or [])
346
+ if r and r.email_address
347
+ ],
348
+ "received_datetime": msg.received_date_time.isoformat() if msg.received_date_time else None,
349
+ "body_preview": msg.body_preview,
350
+ "is_read": msg.is_read,
351
+ "has_attachments": msg.has_attachments,
352
+ "importance": str(msg.importance) if msg.importance else None,
353
+ "web_link": msg.web_link
354
+ }
355
+
356
+ # Add attachment info if requested
357
+ if include_attachments and msg.has_attachments and hasattr(msg, 'attachments'):
358
+ result_item["attachments"] = [
359
+ {
360
+ "name": att.name,
361
+ "size": att.size,
362
+ "content_type": att.content_type
363
+ }
364
+ for att in (msg.attachments or [])
365
+ ]
366
+
367
+ results.append(result_item)
368
+
369
+ self.logger.info(f"Found {len(results)} emails matching query: {query}")
370
+
371
+ return {
372
+ "query": query,
373
+ "folder": folder,
374
+ "total_results": len(results),
375
+ "messages": results
376
+ }
377
+
378
+ except Exception as e:
379
+ self.logger.error(f"Failed to search emails: {e}", exc_info=True)
380
+ raise
381
+
382
+ # ============================================================================
383
+ # SEND EMAIL TOOL
384
+ # ============================================================================
385
+
386
+ class SendEmailArgs(O365ToolArgsSchema):
387
+ """Arguments for sending an email."""
388
+ subject: str = Field(
389
+ description="Email subject line"
390
+ )
391
+ body: str = Field(
392
+ description="Email body content (can be HTML or plain text)"
393
+ )
394
+ to_recipients: List[str] = Field(
395
+ description="List of recipient email addresses"
396
+ )
397
+ cc_recipients: Optional[List[str]] = Field(
398
+ default=None,
399
+ description="List of CC recipient email addresses"
400
+ )
401
+ bcc_recipients: Optional[List[str]] = Field(
402
+ default=None,
403
+ description="List of BCC recipient email addresses"
404
+ )
405
+ importance: Optional[str] = Field(
406
+ default="normal",
407
+ description="Email importance: 'low', 'normal', or 'high'"
408
+ )
409
+ is_html: bool = Field(
410
+ default=False,
411
+ description="Whether the body is HTML (True) or plain text (False)"
412
+ )
413
+ save_to_sent_items: bool = Field(
414
+ default=True,
415
+ description="Whether to save a copy in Sent Items folder"
416
+ )
417
+
418
+
419
+ class SendEmailTool(O365Tool):
420
+ """
421
+ Tool for sending emails directly in Office365.
422
+
423
+ This tool sends an email immediately without creating a draft.
424
+ The email is sent and optionally saved to the Sent Items folder.
425
+
426
+ Examples:
427
+ # Send a simple email
428
+ result = await tool.run(
429
+ subject="Quick Update",
430
+ body="Just wanted to let you know...",
431
+ to_recipients=["colleague@company.com"]
432
+ )
433
+
434
+ # Send HTML email with CC
435
+ result = await tool.run(
436
+ subject="Newsletter",
437
+ body="<h2>This Month's Updates</h2><p>Content here...</p>",
438
+ to_recipients=["subscriber@email.com"],
439
+ cc_recipients=["team@company.com"],
440
+ importance="high",
441
+ is_html=True
442
+ )
443
+
444
+ # Send without saving to Sent Items
445
+ result = await tool.run(
446
+ subject="Temporary Message",
447
+ body="This won't be saved in Sent Items",
448
+ to_recipients=["user@company.com"],
449
+ save_to_sent_items=False
450
+ )
451
+ """
452
+
453
+ name: str = "send_email"
454
+ description: str = (
455
+ "Send an email directly through Office365. "
456
+ "The email is sent immediately and optionally saved to Sent Items."
457
+ )
458
+ args_schema: Type[BaseModel] = SendEmailArgs
459
+
460
+ async def _execute_graph_operation(
461
+ self,
462
+ client: O365Client,
463
+ **kwargs
464
+ ) -> Dict[str, Any]:
465
+ """
466
+ Send an email using Microsoft Graph API.
467
+
468
+ Args:
469
+ client: Authenticated O365Client
470
+ **kwargs: Email parameters
471
+
472
+ Returns:
473
+ Dict with send confirmation
474
+ """
475
+ # Extract parameters
476
+ subject = kwargs.get('subject')
477
+ body_content = kwargs.get('body')
478
+ to_recipients = kwargs.get('to_recipients', [])
479
+ cc_recipients = kwargs.get('cc_recipients')
480
+ bcc_recipients = kwargs.get('bcc_recipients')
481
+ importance_str = kwargs.get('importance', 'normal')
482
+ is_html = kwargs.get('is_html', False)
483
+ save_to_sent = kwargs.get('save_to_sent_items', True)
484
+ user_id = kwargs.get('user_id')
485
+
486
+ try:
487
+ # Get user context
488
+ mailbox = client.get_user_context(user_id=user_id)
489
+
490
+ # Build message object
491
+ message = Message()
492
+ message.subject = subject
493
+
494
+ # Set body
495
+ message.body = ItemBody()
496
+ message.body.content = body_content
497
+ message.body.content_type = BodyType.Html if is_html else BodyType.Text
498
+
499
+ # Helper function to create recipient
500
+ def create_recipient(email: str) -> Recipient:
501
+ recipient = Recipient()
502
+ recipient.email_address = EmailAddress()
503
+ recipient.email_address.address = email
504
+ return recipient
505
+
506
+ # Set recipients
507
+ message.to_recipients = [create_recipient(email) for email in to_recipients]
508
+
509
+ if cc_recipients:
510
+ message.cc_recipients = [create_recipient(email) for email in cc_recipients]
511
+
512
+ if bcc_recipients:
513
+ message.bcc_recipients = [create_recipient(email) for email in bcc_recipients]
514
+
515
+ # Set importance
516
+ importance_map = {
517
+ 'low': Importance.Low,
518
+ 'normal': Importance.Normal,
519
+ 'high': Importance.High
520
+ }
521
+ message.importance = importance_map.get(importance_str.lower(), Importance.Normal)
522
+
523
+ # Create the request body for send_mail
524
+ request_body = SendMailPostRequestBody()
525
+ request_body.message = message
526
+ request_body.save_to_sent_items = save_to_sent
527
+
528
+ # Send the email
529
+ self.logger.info(f"Sending email to {to_recipients}")
530
+ await mailbox.send_mail.post(body=request_body)
531
+
532
+ self.logger.info(f"Successfully sent email: {subject}")
533
+
534
+ return {
535
+ "status": "sent",
536
+ "subject": subject,
537
+ "to_recipients": to_recipients,
538
+ "cc_recipients": cc_recipients or [],
539
+ "bcc_recipients": bcc_recipients or [],
540
+ "importance": importance_str,
541
+ "is_html": is_html,
542
+ "sent_datetime": datetime.now().isoformat(),
543
+ "saved_to_sent_items": save_to_sent
544
+ }
545
+
546
+ except Exception as e:
547
+ self.logger.error(f"Failed to send email: {e}", exc_info=True)
548
+ raise
549
+
550
+ # ============================================================================
551
+ # LIST MESSAGES TOOL
552
+ # ============================================================================
553
+
554
+ class ListMessagesArgs(O365ToolArgsSchema):
555
+ """Arguments for listing email messages."""
556
+ folder: str = Field(
557
+ default="inbox",
558
+ description="Folder to list messages from: 'inbox', 'sentitems', 'drafts', 'deleteditems', or folder ID"
559
+ )
560
+ top: int = Field(
561
+ default=10,
562
+ description="Maximum number of messages to return (1-1000)"
563
+ )
564
+ filter_query: Optional[str] = Field(
565
+ default=None,
566
+ description="OData filter query (e.g., 'isRead eq false', 'hasAttachments eq true')"
567
+ )
568
+ order_by: str = Field(
569
+ default="receivedDateTime desc",
570
+ description="Sort order (e.g., 'receivedDateTime desc', 'subject asc')"
571
+ )
572
+ select_fields: Optional[List[str]] = Field(
573
+ default=None,
574
+ description="Specific fields to retrieve. If None, returns default fields."
575
+ )
576
+
577
+
578
+ class ListMessagesTool(O365Tool):
579
+ """
580
+ Tool for listing email messages from a specified folder.
581
+
582
+ This tool allows you to:
583
+ - List messages from any mail folder (Inbox, Sent Items, etc.)
584
+ - Filter messages by various criteria (read status, sender, date, etc.)
585
+ - Limit the number of results
586
+ - Order results by different fields
587
+ - Select specific fields to retrieve
588
+
589
+ Filter query examples:
590
+ - "isRead eq false" - Unread messages
591
+ - "hasAttachments eq true" - Messages with attachments
592
+ - "from/emailAddress/address eq 'user@example.com'" - From specific sender
593
+ - "receivedDateTime ge 2025-10-16T00:00:00Z" - Received after date
594
+ - "importance eq 'high'" - High importance messages
595
+
596
+ Examples:
597
+ # List recent messages
598
+ result = await tool.run(
599
+ folder="inbox",
600
+ top=20
601
+ )
602
+
603
+ # List unread messages
604
+ result = await tool.run(
605
+ folder="inbox",
606
+ filter_query="isRead eq false"
607
+ )
608
+
609
+ # List messages from specific sender
610
+ result = await tool.run(
611
+ folder="inbox",
612
+ filter_query="from/emailAddress/address eq 'boss@company.com'",
613
+ top=10
614
+ )
615
+ """
616
+
617
+ name: str = "list_messages"
618
+ description: str = (
619
+ "List email messages from a specified folder with optional filtering and sorting. "
620
+ "Supports OData filters for advanced queries."
621
+ )
622
+ args_schema: Type[BaseModel] = ListMessagesArgs
623
+
624
+ async def _execute_graph_operation(
625
+ self,
626
+ client: O365Client,
627
+ **kwargs
628
+ ) -> Dict[str, Any]:
629
+ """
630
+ List messages using Microsoft Graph API.
631
+
632
+ Args:
633
+ client: Authenticated O365Client
634
+ **kwargs: List parameters
635
+
636
+ Returns:
637
+ Dict with list of messages
638
+ """
639
+ folder = kwargs.get('folder', 'inbox')
640
+ top = min(kwargs.get('top', 10), 1000) # Cap at 1000
641
+ filter_query = kwargs.get('filter_query')
642
+ order_by = kwargs.get('order_by', 'receivedDateTime desc')
643
+ select_fields = kwargs.get('select_fields')
644
+ user_id = kwargs.get('user_id')
645
+
646
+ try:
647
+ # Get user context
648
+ mailbox = client.get_user_context(user_id=user_id)
649
+
650
+ # Default fields to select
651
+ default_fields = [
652
+ 'id', 'subject', 'from', 'toRecipients', 'receivedDateTime',
653
+ 'sentDateTime', 'hasAttachments', 'importance', 'isRead',
654
+ 'bodyPreview', 'internetMessageId', 'webLink'
655
+ ]
656
+
657
+ fields = select_fields or default_fields
658
+
659
+ # Apply folder filter and get appropriate request builder
660
+ folder_map = {
661
+ 'inbox': 'inbox',
662
+ 'sentitems': 'sentitems',
663
+ 'drafts': 'drafts',
664
+ 'deleteditems': 'deleteditems'
665
+ }
666
+
667
+ if folder.lower() in folder_map:
668
+ folder_name = folder_map[folder.lower()]
669
+ request_builder = mailbox.mail_folders.by_mail_folder_id(folder_name).messages
670
+ else:
671
+ # Try as direct folder ID or use inbox as fallback
672
+ request_builder = mailbox.messages
673
+
674
+ # Build query parameters
675
+ query_params_obj = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
676
+ top=top,
677
+ select=fields,
678
+ orderby=[order_by]
679
+ )
680
+
681
+ # Add filter if provided
682
+ if filter_query:
683
+ query_params_obj.filter = filter_query
684
+
685
+ # Wrap in RequestConfiguration
686
+ request_config = RequestConfiguration(
687
+ query_parameters=query_params_obj
688
+ )
689
+
690
+ # Execute request
691
+ self.logger.info(f"Listing messages: folder='{folder}', top={top}, filter='{filter_query}'")
692
+ messages = await request_builder.get(request_configuration=request_config)
693
+
694
+ # Format results
695
+ results = []
696
+ if messages and messages.value:
697
+ for msg in messages.value:
698
+ result_item = {
699
+ "id": msg.id,
700
+ "subject": msg.subject or "(No subject)",
701
+ "from": msg.from_.email_address.address if msg.from_ and msg.from_.email_address else None,
702
+ "from_name": msg.from_.email_address.name if msg.from_ and msg.from_.email_address else None,
703
+ "to": [
704
+ r.email_address.address
705
+ for r in (msg.to_recipients or [])
706
+ if r and r.email_address
707
+ ],
708
+ "received_datetime": msg.received_date_time.isoformat() if msg.received_date_time else None,
709
+ "sent_datetime": msg.sent_date_time.isoformat() if msg.sent_date_time else None,
710
+ "has_attachments": msg.has_attachments,
711
+ "importance": str(msg.importance) if msg.importance else None,
712
+ "is_read": msg.is_read,
713
+ "body_preview": msg.body_preview,
714
+ "internet_message_id": msg.internet_message_id,
715
+ "web_link": msg.web_link
716
+ }
717
+ results.append(result_item)
718
+
719
+ self.logger.info(f"Found {len(results)} messages in folder '{folder}'")
720
+
721
+ return {
722
+ "folder": folder,
723
+ "total_results": len(results),
724
+ "messages": results
725
+ }
726
+
727
+ except Exception as e:
728
+ self.logger.error(f"Failed to list messages: {e}", exc_info=True)
729
+ raise
730
+
731
+
732
+ # ============================================================================
733
+ # GET MESSAGE TOOL
734
+ # ============================================================================
735
+
736
+ class GetMessageArgs(O365ToolArgsSchema):
737
+ """Arguments for retrieving a specific message."""
738
+ message_id: str = Field(
739
+ description="The ID of the message to retrieve"
740
+ )
741
+ include_body: bool = Field(
742
+ default=True,
743
+ description="Whether to include the full message body content"
744
+ )
745
+
746
+
747
+ class GetMessageTool(O365Tool):
748
+ """
749
+ Tool for retrieving a specific email message by its ID.
750
+
751
+ This tool retrieves complete information about a single message, including:
752
+ - Full message headers (subject, sender, recipients, dates)
753
+ - Message body content (if include_body=True)
754
+ - Attachment information
755
+ - Message metadata (read status, importance, conversation ID)
756
+
757
+ Use this tool when you need detailed information about a specific message,
758
+ such as reading the full content or checking for attachments.
759
+
760
+ Examples:
761
+ # Get message with body
762
+ result = await tool.run(
763
+ message_id="AAMkAGI...",
764
+ include_body=True
765
+ )
766
+
767
+ # Get message metadata only (faster)
768
+ result = await tool.run(
769
+ message_id="AAMkAGI...",
770
+ include_body=False
771
+ )
772
+ """
773
+
774
+ name: str = "get_message"
775
+ description: str = (
776
+ "Retrieve a specific email message by its ID with complete details. "
777
+ "Can include full body content and attachment information."
778
+ )
779
+ args_schema: Type[BaseModel] = GetMessageArgs
780
+
781
+ async def _execute_graph_operation(
782
+ self,
783
+ client: O365Client,
784
+ **kwargs
785
+ ) -> Dict[str, Any]:
786
+ """
787
+ Get a specific message using Microsoft Graph API.
788
+
789
+ Args:
790
+ client: Authenticated O365Client
791
+ **kwargs: Get parameters
792
+
793
+ Returns:
794
+ Dict with message details
795
+ """
796
+ message_id = kwargs.get('message_id')
797
+ include_body = kwargs.get('include_body', True)
798
+ user_id = kwargs.get('user_id')
799
+
800
+ try:
801
+ # Get user context
802
+ mailbox = client.get_user_context(user_id=user_id)
803
+
804
+ # Select fields based on whether body is needed
805
+ if include_body:
806
+ select_fields = [
807
+ 'id', 'subject', 'from', 'toRecipients', 'ccRecipients', 'bccRecipients',
808
+ 'receivedDateTime', 'sentDateTime', 'hasAttachments', 'importance', 'isRead',
809
+ 'body', 'bodyPreview', 'internetMessageId', 'conversationId', 'webLink'
810
+ ]
811
+ else:
812
+ select_fields = [
813
+ 'id', 'subject', 'from', 'toRecipients', 'ccRecipients', 'bccRecipients',
814
+ 'receivedDateTime', 'sentDateTime', 'hasAttachments', 'importance', 'isRead',
815
+ 'bodyPreview', 'internetMessageId', 'conversationId', 'webLink'
816
+ ]
817
+
818
+ # Build query parameters
819
+ query_params_obj = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
820
+ select=select_fields
821
+ )
822
+
823
+ request_config = RequestConfiguration(
824
+ query_parameters=query_params_obj
825
+ )
826
+
827
+ # Get the message
828
+ self.logger.info(f"Getting message: {message_id}")
829
+ message = await mailbox.messages.by_message_id(message_id).get(
830
+ request_configuration=request_config
831
+ )
832
+
833
+ if not message:
834
+ raise ValueError(f"Message {message_id} not found")
835
+
836
+ # Format result
837
+ result = {
838
+ "id": message.id,
839
+ "subject": message.subject or "(No subject)",
840
+ "from": message.from_.email_address.address if message.from_ and message.from_.email_address else None,
841
+ "from_name": message.from_.email_address.name if message.from_ and message.from_.email_address else None,
842
+ "to_recipients": [
843
+ r.email_address.address
844
+ for r in (message.to_recipients or [])
845
+ if r and r.email_address
846
+ ],
847
+ "cc_recipients": [
848
+ r.email_address.address
849
+ for r in (message.cc_recipients or [])
850
+ if r and r.email_address
851
+ ],
852
+ "bcc_recipients": [
853
+ r.email_address.address
854
+ for r in (message.bcc_recipients or [])
855
+ if r and r.email_address
856
+ ],
857
+ "received_datetime": message.received_date_time.isoformat() if message.received_date_time else None,
858
+ "sent_datetime": message.sent_date_time.isoformat() if message.sent_date_time else None,
859
+ "has_attachments": message.has_attachments,
860
+ "importance": str(message.importance) if message.importance else None,
861
+ "is_read": message.is_read,
862
+ "body_preview": message.body_preview,
863
+ "internet_message_id": message.internet_message_id,
864
+ "conversation_id": message.conversation_id,
865
+ "web_link": message.web_link
866
+ }
867
+
868
+ # Add body if requested
869
+ if include_body and message.body:
870
+ result["body"] = {
871
+ "content_type": str(message.body.content_type) if message.body.content_type else "text",
872
+ "content": message.body.content or ""
873
+ }
874
+
875
+ self.logger.info(f"Retrieved message: {message.subject}")
876
+
877
+ return result
878
+
879
+ except Exception as e:
880
+ self.logger.error(f"Failed to get message {message_id}: {e}", exc_info=True)
881
+ raise
882
+
883
+
884
+ # ============================================================================
885
+ # DOWNLOAD ATTACHMENT TOOL
886
+ # ============================================================================
887
+
888
+ class DownloadAttachmentArgs(O365ToolArgsSchema):
889
+ """Arguments for downloading an email attachment."""
890
+ message_id: str = Field(
891
+ description="The ID of the message containing the attachment"
892
+ )
893
+ attachment_id: str = Field(
894
+ description="The ID of the attachment to download"
895
+ )
896
+ destination: str = Field(
897
+ description="Local path where the attachment should be saved"
898
+ )
899
+
900
+
901
+ class DownloadAttachmentTool(O365Tool):
902
+ """
903
+ Tool for downloading email attachments to local storage.
904
+
905
+ This tool downloads a specific attachment from an email message and saves it
906
+ to a specified location on the local filesystem.
907
+
908
+ Before downloading, you should:
909
+ 1. Use GetMessageTool to retrieve the message and check hasAttachments
910
+ 2. List the attachments to get their IDs and names
911
+ 3. Use this tool to download specific attachments
912
+
913
+ The tool will:
914
+ - Create parent directories if they don't exist
915
+ - Decode and save the attachment content
916
+ - Return the path where the file was saved
917
+
918
+ Examples:
919
+ # Download attachment
920
+ result = await tool.run(
921
+ message_id="AAMkAGI...",
922
+ attachment_id="AAMkAGI...Attach...",
923
+ destination="/tmp/documents/report.pdf"
924
+ )
925
+ """
926
+
927
+ name: str = "download_attachment"
928
+ description: str = (
929
+ "Download an email attachment to local storage. "
930
+ "Saves the attachment file to the specified destination path."
931
+ )
932
+ args_schema: Type[BaseModel] = DownloadAttachmentArgs
933
+
934
+ async def _execute_graph_operation(
935
+ self,
936
+ client: O365Client,
937
+ **kwargs
938
+ ) -> Dict[str, Any]:
939
+ """
940
+ Download an attachment using Microsoft Graph API.
941
+
942
+ Args:
943
+ client: Authenticated O365Client
944
+ **kwargs: Download parameters
945
+
946
+ Returns:
947
+ Dict with download confirmation and path
948
+ """
949
+ message_id = kwargs.get('message_id')
950
+ attachment_id = kwargs.get('attachment_id')
951
+ destination = kwargs.get('destination')
952
+ user_id = kwargs.get('user_id')
953
+
954
+ try:
955
+ # Get user context
956
+ mailbox = client.get_user_context(user_id=user_id)
957
+
958
+ # Get attachment info
959
+ self.logger.info(f"Getting attachment {attachment_id} from message {message_id}")
960
+ attachment = await mailbox.messages.by_message_id(message_id)\
961
+ .attachments.by_attachment_id(attachment_id).get()
962
+
963
+ if not attachment:
964
+ raise ValueError(f"Attachment {attachment_id} not found")
965
+
966
+ # Prepare destination path
967
+ destination_path = Path(destination)
968
+ destination_path.parent.mkdir(parents=True, exist_ok=True)
969
+
970
+ # Handle different attachment types
971
+ if hasattr(attachment, 'content_bytes') and attachment.content_bytes:
972
+ # File attachment with content
973
+ content = base64.b64decode(attachment.content_bytes)
974
+ async with aiofiles.open(destination_path, "wb") as f:
975
+ await f.write(content)
976
+
977
+ self.logger.info(f"Downloaded attachment '{attachment.name}' to {destination_path}")
978
+
979
+ return {
980
+ "status": "downloaded",
981
+ "attachment_id": attachment.id,
982
+ "attachment_name": attachment.name,
983
+ "size": attachment.size,
984
+ "content_type": attachment.content_type,
985
+ "destination": str(destination_path)
986
+ }
987
+ else:
988
+ raise ValueError(f"Attachment {attachment_id} has no downloadable content")
989
+
990
+ except Exception as e:
991
+ self.logger.error(f"Failed to download attachment {attachment_id}: {e}", exc_info=True)
992
+ raise