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,900 @@
1
+ from datetime import datetime
2
+ from pathlib import Path
3
+ from typing import Tuple, Union, List, Dict, Any, Optional, Callable, Sequence, Awaitable
4
+ import functools
5
+ from io import BytesIO
6
+ import tempfile
7
+ import aiofiles
8
+ # Parrot:
9
+ from aiohttp import web
10
+ from datamodel.parsers.json import json_encoder # noqa pylint: disable=E0611
11
+ # AsyncDB:
12
+ from asyncdb import AsyncDB
13
+ # Requirements from Notify API:
14
+ from notify import Notify # para envio local
15
+ from notify.providers.teams import Teams
16
+ from notify.server import NotifyClient # envio a traves de los workers
17
+ from notify.models import Actor, Chat, TeamsCard, TeamsChannel
18
+ from notify.conf import NOTIFY_REDIS, NOTIFY_WORKER_STREAM, NOTIFY_CHANNEL
19
+ # Navigator:
20
+ from navconfig import config, BASE_DIR
21
+ from navconfig.logging import logging
22
+ from navigator_session import get_session
23
+ # Auth
24
+ from navigator_auth.decorators import (
25
+ is_authenticated,
26
+ )
27
+ from navigator_auth.conf import AUTH_SESSION_OBJECT
28
+ # Tasker:
29
+ from navigator.background import (
30
+ BackgroundService,
31
+ TaskWrapper,
32
+ JobRecord
33
+ )
34
+ from navigator.services.ws import WebSocketManager
35
+ from navigator.applications.base import BaseApplication # pylint: disable=E0611
36
+ from navigator.views import BaseView
37
+ from navigator.responses import JSONResponse
38
+ from navigator.types import WebApp # pylint: disable=E0611
39
+ from navigator.conf import CACHE_URL, default_dsn
40
+ # Parrot:
41
+ from ...bots.agent import BasicAgent
42
+ from ...tools.abstract import AbstractTool
43
+ from ...models.responses import AgentResponse, AIMessage
44
+ from ...clients.gpt import OpenAIClient
45
+ from ...clients.claude import ClaudeClient
46
+ from ...conf import STATIC_DIR, AGENTS_BOTS_PROMPT_DIR, AGENTS_DIR
47
+
48
+
49
+ class RedisWriter:
50
+ """RedisWriter class."""
51
+ def __init__(self):
52
+ self.conn = AsyncDB('redis', dsn=CACHE_URL)
53
+
54
+ @property
55
+ def redis(self):
56
+ """Get Redis Connection."""
57
+ return self.conn
58
+
59
+
60
+ class JobWSManager(WebSocketManager):
61
+ """
62
+ Extends the generic WebSocketManager with one helper that sends
63
+ a direct message to the user owning a finished job.
64
+ """
65
+ async def notify_job_done(
66
+ self,
67
+ *,
68
+ user_id: int | str,
69
+ job_id: str,
70
+ status: str,
71
+ result: Optional[Any] = None,
72
+ error: Optional[str] = None,
73
+ ) -> None:
74
+ """
75
+ Push a JSON message to every open WS belonging to `user_id`.
76
+ """
77
+ payload = {
78
+ "type": "job_status",
79
+ "job_id": job_id,
80
+ "status": status, # "done" / "failed"
81
+ "result": result,
82
+ "error": error,
83
+ }
84
+ message = json_encoder(payload)
85
+ delivered = 0
86
+ for ws, info in list(self.clients.items()):
87
+ if ws.closed:
88
+ continue
89
+ if info.get("user_id") == str(user_id):
90
+ await ws.send_str(message)
91
+ delivered += 1
92
+
93
+ if delivered == 0:
94
+ self.logger.debug(
95
+ "No active WS for user_id=%s when job %s finished", user_id, job_id
96
+ )
97
+
98
+ def auth_groups(
99
+ allowed: Sequence[str]
100
+ ) -> Callable[[Callable[..., Awaitable]], Callable[..., Awaitable]]:
101
+ """Ensure the request is authenticated *and* the user belongs
102
+ to at least one of `allowed` groups.
103
+ """
104
+ def decorator(fn: Union[Any, Any]) -> Any:
105
+ # 1️⃣ first wrap the target function with the base auth check
106
+ fn = AgentHandler.service_auth(fn)
107
+ @functools.wraps(fn)
108
+ async def wrapper(self, *args, **kwargs):
109
+ # At this point `service_auth` has already:
110
+ # * verified the session
111
+ # * populated `self._session` and `self._superuser`
112
+ if self._superuser:
113
+ # If the user is a superuser, skip group checks
114
+ return await fn(self, *args, **kwargs)
115
+ # Now add the group check
116
+ user_groups = set(self._session.get("groups", []))
117
+ if not user_groups.intersection(allowed):
118
+ self.error(
119
+ response={
120
+ "error": "Forbidden",
121
+ "message": f"User lacks required group(s): {allowed}"
122
+ },
123
+ status=403
124
+ )
125
+ return await fn(self, *args, **kwargs)
126
+ return wrapper
127
+ return decorator
128
+
129
+ def auth_by_attribute(
130
+ allowed: Sequence[str],
131
+ attribute: str = 'job_code'
132
+ ) -> Callable[[Callable[..., Awaitable]], Callable[..., Awaitable]]:
133
+ """Ensure the request is authenticated *and* the user belongs
134
+ to at least one of `allowed` Job Codes.
135
+ """
136
+ def decorator(fn: Union[Any, Any]) -> Any:
137
+ # 1️⃣ first wrap the target function with the base auth check
138
+ fn = AgentHandler.service_auth(fn)
139
+ @functools.wraps(fn)
140
+ async def wrapper(self, *args, **kwargs):
141
+ # At this point `service_auth` has already:
142
+ # * verified the session
143
+ # * populated `self._session` and `self._superuser`
144
+ if self._superuser:
145
+ # If the user is a superuser, skip job code checks
146
+ return await fn(self, *args, **kwargs)
147
+ # Now add the jobcode check
148
+ userinfo = self._session.get(AUTH_SESSION_OBJECT, {})
149
+ attr = userinfo.get(attribute, None)
150
+ if not attr:
151
+ self.error(
152
+ response={
153
+ "error": "Forbidden",
154
+ "message": f"User does not have a valid {attribute}."
155
+ },
156
+ status=403
157
+ )
158
+ if not attr in allowed:
159
+ self.error(
160
+ response={
161
+ "error": "Forbidden",
162
+ "message": f"User lacks required attribute(s) ({attribute})."
163
+ },
164
+ status=403
165
+ )
166
+ return await fn(self, *args, **kwargs)
167
+ return wrapper
168
+ return decorator
169
+
170
+
171
+ @is_authenticated()
172
+ class AgentHandler(BaseView):
173
+ """Abstract class for chatbot/agent handlers.
174
+
175
+ Provide a complete abstraction for exposing AI Agents as a REST API.
176
+ """
177
+ app: web.Application = None
178
+ agent_name: str = "NextStop"
179
+ agent_id: str = "nextstop"
180
+ _tools: List[AbstractTool] = []
181
+ _agent: BasicAgent = None
182
+ _use_llm: str = 'google'
183
+ _use_model: str = 'gemini-2.5-pro'
184
+ # signals
185
+ on_startup: Optional[Callable] = None
186
+ on_shutdown: Optional[Callable] = None
187
+ on_cleanup: Optional[Callable] = None
188
+
189
+ # Define base routes - can be overridden in subclasses
190
+ base_route: str = None # e.g., "/api/v1/agent/{agent_name}"
191
+ additional_routes: List[Dict[str, Any]] = [] # Custom routes
192
+ _agent_class: type = BasicAgent # Default agent class
193
+ _agent_response: type = AgentResponse # Default response type
194
+
195
+ def __init__(
196
+ self,
197
+ request: web.Request = None,
198
+ *args,
199
+ app: web.Application = None,
200
+ **kwargs
201
+ ):
202
+ if request is not None:
203
+ super().__init__(request, *args, **kwargs)
204
+ self.logger = logging.getLogger(
205
+ f"{self.__class__.__module__}.{self.__class__.__name__}"
206
+ )
207
+ self.redis = RedisWriter()
208
+ self.gcs = None # GCS Manager
209
+ self.s3 = None # S3 Manager
210
+ # Session and User ID
211
+ self._session: Optional[Dict[str, Any]] = None
212
+ self._userid: Optional[str] = None
213
+ # Temporal Agent Uploader
214
+ self.temp_dir = self.create_temp_directory()
215
+ self.app = app
216
+ self._program: str = kwargs.pop('program_slug', 'parrot') # Default program slug
217
+
218
+ def set_program(self, program_slug: str) -> None:
219
+ """Set the program slug for the agent."""
220
+ self._program = program_slug
221
+
222
+ def setup(
223
+ self,
224
+ app: Union[WebApp, web.Application],
225
+ route: List[Dict[Any, str]] = None
226
+ ) -> None:
227
+ """Setup the handler with the application and route.
228
+
229
+ Args:
230
+ app (Union[WebApp, web.Application]): The web application instance.
231
+ route (List[Dict[Any, str]]): The route configuration.
232
+ """
233
+ if isinstance(app, BaseApplication):
234
+ app = app.get_app()
235
+ elif isinstance(app, WebApp):
236
+ app = app # register the app into the Extension
237
+ else:
238
+ raise TypeError(
239
+ "Expected app to be an instance of BaseApplication."
240
+ )
241
+ self.app = app
242
+ # Register the main view class route
243
+ if route:
244
+ self.app.router.add_view(route, self.__class__)
245
+ elif self.base_route:
246
+ self.app.router.add_view(self.base_route, self.__class__)
247
+
248
+ # And register any additional custom routes
249
+ self._register_additional_routes()
250
+
251
+ # Tasker: Background Task Manager (if not already registered):
252
+ if 'background_service' not in app:
253
+ BackgroundService(
254
+ app=self.app,
255
+ max_workers=10,
256
+ queue_size=10,
257
+ tracker_type='redis', # Use 'redis' for Redis-based tracking
258
+ service_name=f"{self.agent_name}_tasker"
259
+ )
260
+ # Tool definition:
261
+ self.define_tools()
262
+
263
+ # Startup and shutdown callbacks
264
+ app.on_startup.append(self.create_agent)
265
+ # Register Signals:
266
+ if callable(self.on_startup):
267
+ app.on_startup.append(self.on_startup)
268
+ if callable(self.on_shutdown):
269
+ app.on_shutdown.append(self.on_shutdown)
270
+ if callable(self.on_cleanup):
271
+ app.on_cleanup.append(self.on_cleanup)
272
+
273
+ async def create_agent(self, app: web.Application):
274
+ self.logger.info("Starting up agent handler...")
275
+ # Initialize the agent
276
+ await self._create_agent(app)
277
+
278
+ def define_tools(self):
279
+ """Define additional tools for the agent."""
280
+ pass
281
+
282
+ def db_connection(
283
+ self,
284
+ driver: str = 'pg',
285
+ dsn: str = None,
286
+ credentials: dict = None
287
+ ) -> AsyncDB:
288
+ """Return a database connection."""
289
+ if not dsn:
290
+ dsn = config.get(f'{driver.upper()}_DSN', fallback=default_dsn)
291
+ if not dsn and credentials:
292
+ dsn = credentials.get('dsn', default_dsn)
293
+ if not dsn:
294
+ raise ValueError(
295
+ f"DSN for {driver} is not provided."
296
+ )
297
+ return AsyncDB(driver, dsn=dsn, credentials=credentials)
298
+
299
+ async def register_background_task(
300
+ self,
301
+ task: Callable[..., Awaitable],
302
+ request: web.Request = None,
303
+ done_callback: Optional[Callable[..., Awaitable]] = None,
304
+ *args,
305
+ **kwargs
306
+ ) -> JobRecord:
307
+ """Register a background task with the BackgroundService.
308
+ Add an optional task wrapper to handle the task execution.
309
+ Args:
310
+ task (Callable[..., Awaitable]): The task to be executed.
311
+ request (web.Request, optional): The request object. Defaults to None.
312
+ done_callback (Optional[Callable[..., Awaitable]], optional):
313
+ A callback to be called when the task is done. Defaults to None.
314
+ *args: Positional arguments to pass to the task.
315
+ **kwargs: Keyword arguments to pass to the task.
316
+ Returns:
317
+ JobRecord: The job record containing the task ID and other details.
318
+ Raises:
319
+ RuntimeError: If the request is not available.
320
+ """
321
+ if not request:
322
+ request = self.request
323
+ if not request:
324
+ raise RuntimeError("Request is not available.")
325
+ # Get the BackgroundService instance
326
+ service: BackgroundService = request.app['background_service']
327
+ # Create a TaskWrapper instance
328
+ task = TaskWrapper(
329
+ *args,
330
+ fn=task,
331
+ logger=self.logger,
332
+ **kwargs
333
+ )
334
+ task.add_callback(done_callback)
335
+ # Register the task with the service
336
+ job = await service.submit(task)
337
+ self.logger.notice(
338
+ f"Registered background task: {task!r} with ID: {job.task_id}"
339
+ )
340
+ return job
341
+
342
+ async def find_jobs(self, request: web.Request) -> web.Response:
343
+ """Return Jobs by User."""
344
+ # Get the BackgroundService instance
345
+ service: BackgroundService = request.app['background_service']
346
+ # get service tracker:
347
+ tracker = service.tracker
348
+ session = await self.get_user_session()
349
+ userid = self.get_userid(session=session)
350
+ if not userid:
351
+ return JSONResponse(
352
+ content=None,
353
+ headers={"x-message": "User ID not found in session."},
354
+ status=401
355
+ )
356
+ search = {
357
+ 'user_id': userid,
358
+ 'agent_name': self.agent_name,
359
+ }
360
+ result = await tracker.find_jobs(
361
+ attrs=search
362
+ )
363
+ if not result:
364
+ return JSONResponse(
365
+ content=None,
366
+ headers={"x-message": "No jobs found for this user."},
367
+ status=204
368
+ )
369
+ return JSONResponse(result)
370
+
371
+ def _register_additional_routes(self):
372
+ """Register additional custom routes defined in the class."""
373
+ for route_config in self.additional_routes:
374
+ method = route_config.get('method', 'GET').upper()
375
+ path = route_config['path']
376
+ handler_name = route_config['handler']
377
+
378
+ # Get the handler method from the class
379
+ handler_method = getattr(self.__class__, handler_name)
380
+
381
+ # Create a wrapper that instantiates the class and calls the method
382
+ async def route_wrapper(request, handler_method=handler_method):
383
+ instance = self.__class__(request)
384
+ return await handler_method(instance, request)
385
+
386
+ # Add the route to the router
387
+ self.app.router.add_route(method, path, route_wrapper)
388
+
389
+ async def get_task_status(self, task_id: str, request: web.Request = None) -> JSONResponse:
390
+ """Get the status of a background task by its ID."""
391
+ req = request or self.request
392
+ if not req:
393
+ raise RuntimeError("Request is not available.")
394
+ service: BackgroundService = req.app['background_service']
395
+ try:
396
+ job = await service.record(task_id)
397
+ if job:
398
+ return JSONResponse(
399
+ {
400
+ "task_id": job.task_id,
401
+ "status": job.status,
402
+ "result": job.result,
403
+ "created_at": job.created_at,
404
+ "started_at": job.started_at,
405
+ "error": job.error,
406
+ "stacktrace": job.stacktrace,
407
+ "attributes": job.attributes,
408
+ "finished_at": job.finished_at,
409
+ "name": job.name,
410
+ # "job": job
411
+ }
412
+ )
413
+ else:
414
+ return JSONResponse(
415
+ {"message": "Task not found"},
416
+ status=404
417
+ )
418
+ except Exception as e:
419
+ return JSONResponse(
420
+ {"error": str(e)},
421
+ status=500
422
+ )
423
+
424
+ def add_route(self, method: str, path: str, handler: str):
425
+ """Instance method to add custom routes."""
426
+ if not hasattr(self, 'additional_routes'):
427
+ self.additional_routes = []
428
+ self.additional_routes.append({
429
+ 'method': method,
430
+ 'path': path,
431
+ 'handler': handler
432
+ })
433
+
434
+ def create_temp_directory(self, name: str = 'documents'):
435
+ """Create the temporary directory for saving Agent Documents."""
436
+ tmp_dir = tempfile.TemporaryDirectory()
437
+ # Create the "documents" subdirectory inside the temporary directory
438
+ p_dir = Path(tmp_dir.name).joinpath(self.agent_name, name)
439
+ p_dir.mkdir(parents=True, exist_ok=True)
440
+ return p_dir
441
+
442
+ async def get_user_session(self):
443
+ """Return the user session from the request."""
444
+ # TODO: Add ABAC Support.
445
+ if not self.request:
446
+ raise RuntimeError("Request is not available.")
447
+ try:
448
+ session = self.request.session
449
+ except AttributeError:
450
+ session = await get_session(self.request)
451
+ if not session:
452
+ self.error(
453
+ response={'message': 'Session not found'},
454
+ status=401
455
+ )
456
+ if not session:
457
+ self.error(
458
+ response={
459
+ "error": "Unauthorized",
460
+ "message": "Hint: maybe need to login and pass Authorization token."
461
+ },
462
+ status=403
463
+ )
464
+ return session
465
+
466
+ def get_userid(
467
+ self,
468
+ session: Optional[Dict[str, Any]] = None,
469
+ idx: str = 'user_id'
470
+ ) -> Optional[str]:
471
+ """Return the user ID from the session."""
472
+ if not session:
473
+ session = self._session
474
+ if not session:
475
+ return None
476
+ if AUTH_SESSION_OBJECT in session:
477
+ return session[AUTH_SESSION_OBJECT][idx]
478
+ return session.get(idx, None)
479
+
480
+ @staticmethod
481
+ def service_auth(fn: Union[Any, Any]) -> Any:
482
+ """Decorator to ensure the service is authenticated."""
483
+ async def wrapper(self, *args, **kwargs):
484
+ session = await self.get_user_session()
485
+ if not session:
486
+ self.error(
487
+ response={
488
+ "error": "Unauthorized",
489
+ "message": "Hint: maybe need to login and pass Authorization token."
490
+ },
491
+ status=403
492
+ )
493
+ # define in-request variables for session and userid
494
+ self._userid = self.get_userid(session)
495
+ self._session = session
496
+ # extract other user information as groups, programs and username:
497
+ userinfo = session.get(AUTH_SESSION_OBJECT, {})
498
+ self._session['email'] = userinfo.get('email', None)
499
+ self._session['username'] = userinfo.get('username', None)
500
+ self._session['programs'] = userinfo.get('programs', [])
501
+ self._session['groups'] = userinfo.get('groups', [])
502
+ self._superuser = userinfo.get('superuser', False)
503
+ self._session['is_superuser'] = self._superuser
504
+ # Set the session in the request for further use
505
+ ## Calling post-authorization Model:
506
+ await self._post_auth(self, *args, **kwargs)
507
+ return await fn(self, *args, **kwargs)
508
+ return wrapper
509
+
510
+ async def _post_auth(self, *args, **kwargs):
511
+ """Post-authorization Model."""
512
+ return True
513
+
514
+ def get_agent(self) -> Any:
515
+ """Return the agent instance."""
516
+ return self._agent
517
+
518
+ def _create_actors(self, recipients: Union[List[dict], dict] = None) -> List:
519
+ if isinstance(recipients, dict):
520
+ recipients = [recipients]
521
+ if not recipients:
522
+ return self.error(
523
+ {'message': 'Recipients are required'},
524
+ status=400
525
+ )
526
+ rcpts = []
527
+ for recipient in recipients:
528
+ name = recipient.get('name', 'Navigator')
529
+ email = recipient.get('address')
530
+ if not email:
531
+ return self.error(
532
+ {'message': 'Address is required'},
533
+ status=400
534
+ )
535
+ rcpt = Actor(**{
536
+ "name": name,
537
+ "account": {
538
+ "address": email
539
+ }
540
+ })
541
+ rcpts.append(rcpt)
542
+ return rcpts
543
+
544
+ async def send_notification(
545
+ self,
546
+ content: str,
547
+ provider: str = 'telegram',
548
+ recipients: Union[List[dict], dict] = None,
549
+ **kwargs
550
+ ) -> Any:
551
+ """Return the notification provider instance."""
552
+ provider = kwargs.get('provider', provider).lower()
553
+ response = []
554
+ if provider == 'telegram':
555
+ sender = Notify(provider)
556
+ chat_id = kwargs.get('chat_id', config.get('TELEGRAM_CHAT_ID'))
557
+ chat = Chat(
558
+ chat_id=chat_id
559
+ )
560
+ async with sender as message: # pylint: disable=E1701 # noqa: E501
561
+ result = await message.send(
562
+ recipient=chat,
563
+ **kwargs
564
+ )
565
+ for r in result:
566
+ res = {
567
+ "provider": provider,
568
+ "message_id": r.message_id,
569
+ "title": r.chat.title,
570
+ }
571
+ response.append(res)
572
+ elif provider == 'email':
573
+ rcpts = self._create_actors(recipients)
574
+ credentials = {
575
+ "hostname": config.get('smtp_host'),
576
+ "port": config.get('smtp_port'),
577
+ "username": config.get('smtp_host_user'),
578
+ "password": config.get('smtp_host_password')
579
+ }
580
+ sender = Notify(provider, **credentials)
581
+ async with sender as message: # pylint: disable=E1701 # noqa: E501
582
+ result = await message.send(
583
+ recipient=rcpts,
584
+ **kwargs,
585
+ **credentials
586
+ )
587
+ for r in result:
588
+ res = {
589
+ "provider": provider,
590
+ "status": r[1],
591
+ }
592
+ response.append(res)
593
+ elif provider == 'teams':
594
+ # Support for private messages:
595
+ sender = Teams(as_user=True)
596
+ if recipients:
597
+ rcpts = self._create_actors(recipients)
598
+ else:
599
+ # by Teams Channel
600
+ default_teams_id = config.get('MS_TEAMS_DEFAULT_TEAMS_ID')
601
+ default_chat_id = config.get('MS_TEAMS_DEFAULT_CHANNEL_ID')
602
+ teams_id = kwargs.pop('teams_id', default_teams_id)
603
+ chat_id = kwargs.pop('chat_id', default_chat_id)
604
+ rcpts = TeamsChannel(
605
+ name="General",
606
+ team_id=teams_id,
607
+ channel_id=chat_id
608
+ )
609
+ card = TeamsCard(
610
+ title=kwargs.get('title'),
611
+ summary=kwargs.get('summary'),
612
+ text=kwargs.get('message'),
613
+ sections=kwargs.get('sections', [])
614
+ )
615
+ async with sender as message: # pylint: disable=E1701 # noqa: E501
616
+ result = await message.send(
617
+ recipient=rcpts,
618
+ message=card
619
+ )
620
+ for r in result:
621
+ res = {
622
+ "message_id": r['id'],
623
+ "webUrl": r['webUrl']
624
+ }
625
+ response.append(res)
626
+ elif provider == 'ses':
627
+ credentials = {
628
+ "aws_access_key_id": config.get('AWS_ACCESS_KEY_ID'),
629
+ "aws_secret_access_key": config.get('AWS_SECRET_ACCESS_KEY'),
630
+ "aws_region_name": config.get('AWS_REGION_NAME'),
631
+ "sender_email": config.get('SENDER_EMAIL')
632
+ }
633
+ message = {
634
+ "provider": "ses",
635
+ "message": content,
636
+ "template": 'email_applied.html',
637
+ **credentials,
638
+ }
639
+ async with NotifyClient(
640
+ redis_url=NOTIFY_REDIS
641
+ ) as client:
642
+ # Stream but using Wrapper:
643
+ await client.stream(
644
+ message,
645
+ stream=NOTIFY_WORKER_STREAM,
646
+ use_wrapper=True
647
+ )
648
+ elif provider == 'mail':
649
+ rcpts = self._create_actors(recipients)
650
+ name = kwargs.pop('name', 'Navigator')
651
+ email = kwargs.pop('address')
652
+ message = {
653
+ "provider": 'email',
654
+ "recipient": {
655
+ "name": name,
656
+ "account": {
657
+ "address": email
658
+ }
659
+ },
660
+ "message": 'Congratulations!',
661
+ "template": 'email_applied.html'
662
+ **kwargs
663
+ }
664
+ async with NotifyClient(
665
+ redis_url=NOTIFY_REDIS
666
+ ) as client:
667
+ for recipient in rcpts:
668
+ message['recipient'] = [recipient]
669
+ await client.publish(
670
+ message,
671
+ channel=NOTIFY_CHANNEL,
672
+ use_wrapper=True
673
+ )
674
+ response = "Message sent"
675
+ else:
676
+ payload = {
677
+ "provider": provider,
678
+ **kwargs
679
+ }
680
+ # Create a NotifyClient instance
681
+ async with NotifyClient(redis_url=NOTIFY_REDIS) as client:
682
+ for recipient in recipients:
683
+ payload['recipient'] = [recipient]
684
+ # Stream but using Wrapper:
685
+ await client.stream(
686
+ payload,
687
+ stream=NOTIFY_WORKER_STREAM,
688
+ use_wrapper=True
689
+ )
690
+ return response
691
+
692
+ async def _handle_uploads(
693
+ self,
694
+ key: str,
695
+ ext: str = '.csv',
696
+ mime_type: str = 'text/csv',
697
+ preserve_filenames: bool = True,
698
+ as_bytes: bool = False
699
+ ) -> Tuple[List[dict], dict]:
700
+ """handle file uploads."""
701
+ # Check if Content-Type is correctly set
702
+ content_type = self.request.headers.get('Content-Type', '')
703
+ if 'multipart/form-data' not in content_type:
704
+ raise web.HTTPUnsupportedMediaType(
705
+ text='Invalid Content-Type. Use multipart/form-data',
706
+ content_type='application/json'
707
+ )
708
+ form_data = {} # return any other form data, if exists.
709
+ uploaded_files_info = []
710
+ try:
711
+ reader = await self.request.multipart()
712
+ except KeyError:
713
+ raise FileNotFoundError(
714
+ "No files found in the request. Please upload a file."
715
+ )
716
+ # Process each part of the multipart request
717
+ async for part in reader:
718
+ if part.filename:
719
+ if key and part.name != key:
720
+ continue
721
+ # Create a temporary file for each uploaded file
722
+ file_ext = Path(part.filename).suffix or ext
723
+ if preserve_filenames:
724
+ # Use the original filename and save in the temp directory
725
+ temp_file_path = Path(tempfile.gettempdir()) / part.filename
726
+ else:
727
+ with tempfile.NamedTemporaryFile(
728
+ delete=False,
729
+ dir=tempfile.gettempdir(),
730
+ suffix=file_ext
731
+ ) as temp_file:
732
+ temp_file_path = Path(temp_file.name)
733
+ # save as a byte stream if required
734
+ file_content = None
735
+ if as_bytes:
736
+ # Read the file content as bytes
737
+ file_content = BytesIO()
738
+ while True:
739
+ chunk = await part.read_chunk()
740
+ if not chunk:
741
+ break
742
+ file_content.write(chunk)
743
+ # Write the bytes to the temp file
744
+ async with aiofiles.open(temp_file_path, 'wb') as f:
745
+ await f.write(file_content.getvalue())
746
+ else:
747
+ # Write the file content
748
+ with temp_file_path.open("wb") as f:
749
+ while True:
750
+ chunk = await part.read_chunk()
751
+ if not chunk:
752
+ break
753
+ f.write(chunk)
754
+ # Get Content-Type header
755
+ mime_type = part.headers.get('Content-Type', mime_type)
756
+ # Store file information
757
+ file_info = {
758
+ 'filename': part.filename,
759
+ 'path': str(temp_file_path),
760
+ 'content_type': mime_type,
761
+ 'size': temp_file_path.stat().st_size
762
+ }
763
+ if file_content is not None:
764
+ file_info['content'] = file_content
765
+ uploaded_files_info.append(file_info)
766
+ else:
767
+ # If it's a form field, add it to the dictionary
768
+ form_field_name = part.name
769
+ form_field_value = await part.text()
770
+ form_data[form_field_name] = form_field_value
771
+ # Check if any files were uploaded
772
+ if not uploaded_files_info:
773
+ raise FileNotFoundError(
774
+ "No files found in the request. Please upload a file."
775
+ )
776
+ # Return the list of uploaded files and any other form data
777
+ return uploaded_files_info, form_data
778
+
779
+ async def _create_agent(
780
+ self,
781
+ app: web.Application
782
+ ) -> BasicAgent:
783
+ """Create and configure a BasicAgent instance."""
784
+ try:
785
+ print('AGENTE > ', self._use_llm, self._use_model)
786
+ agent = self._agent_class(
787
+ name=self.agent_name,
788
+ tools=self._tools,
789
+ llm=self._use_llm,
790
+ model=self._use_model
791
+ )
792
+ print('AGENTE 2 > ', agent)
793
+ agent.set_response(self._agent_response)
794
+ await agent.configure()
795
+ # define the main agent:
796
+ self._agent = agent
797
+ # Store the agent in the application context
798
+ app[self.agent_id] = agent
799
+ self.logger.info(
800
+ f"Agent {self.agent_name} created and configured successfully."
801
+ )
802
+ return agent
803
+ except Exception as e:
804
+ raise RuntimeError(
805
+ f"Failed to create agent {self.agent_name}: {str(e)}"
806
+ ) from e
807
+
808
+ async def open_prompt(self, prompt_file: str = None) -> str:
809
+ """
810
+ Opens a prompt file and returns its content.
811
+ """
812
+ if not prompt_file:
813
+ raise ValueError("No prompt file specified.")
814
+ file = AGENTS_DIR.joinpath(self.agent_id, 'prompts', prompt_file)
815
+ try:
816
+ async with aiofiles.open(file, 'r') as f:
817
+ content = await f.read()
818
+ return content
819
+ except Exception as e:
820
+ raise RuntimeError(
821
+ f"Failed to read prompt file {prompt_file}: {e}"
822
+ ) from e
823
+
824
+ async def ask_agent(
825
+ self,
826
+ query: str = None,
827
+ prompt_file: str = None,
828
+ *args,
829
+ **kwargs
830
+ ) -> Tuple[AgentResponse, AIMessage]:
831
+ """
832
+ Asks the agent a question and returns the response.
833
+ """
834
+ if not self._agent:
835
+ agent = self.request.app[self.agent_id]
836
+ else:
837
+ agent = self._agent
838
+ if not agent:
839
+ raise RuntimeError(
840
+ f"Agent {self.agent_name} is not initialized or not found."
841
+ )
842
+ userid = self._userid if self._userid else self.request.session.get('user_id', None)
843
+ if not userid:
844
+ raise RuntimeError(
845
+ "User ID is not set in the session."
846
+ )
847
+ if not agent:
848
+ raise RuntimeError(
849
+ f"Agent {self.agent_name} is not initialized or not found."
850
+ )
851
+ # query:
852
+ if not query:
853
+ # extract the query from the prompt file if provided:
854
+ if prompt_file:
855
+ query = await self.open_prompt(prompt_file)
856
+ elif hasattr(self.request, 'query') and 'query' in self.request.query:
857
+ query = self.request.query.get('query', None)
858
+ elif hasattr(self.request, 'json'):
859
+ data = await self.request.json()
860
+ query = data.get('query', None)
861
+ elif hasattr(self.request, 'data'):
862
+ data = await self.request.data()
863
+ query = data.get('query', None)
864
+ elif hasattr(self.request, 'text'):
865
+ query = self.request.text
866
+ else:
867
+ raise ValueError(
868
+ "No query provided and no prompt file specified."
869
+ )
870
+ if not query:
871
+ raise ValueError(
872
+ "No query provided or found in the request."
873
+ )
874
+ try:
875
+ response = await agent.conversation(
876
+ question=query,
877
+ max_tokens=8192
878
+ )
879
+ if isinstance(response, Exception):
880
+ raise response
881
+ except Exception as e:
882
+ print(f"Error invoking agent: {e}")
883
+ raise RuntimeError(
884
+ f"Failed to generate report due to an error in the agent invocation: {e}"
885
+ )
886
+
887
+ # Create the response object
888
+ final_report = response.output.strip()
889
+ # when final report is made, then generate the transcript, pdf and podcast:
890
+ response_data = self._agent_response(
891
+ user_id=str(userid),
892
+ agent_name=self.agent_name,
893
+ attributes=kwargs.pop('attributes', {}),
894
+ data=final_report,
895
+ status="success",
896
+ created_at=datetime.now(),
897
+ output=response.output,
898
+ **kwargs
899
+ )
900
+ return response_data, response