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,910 @@
1
+ """
2
+ PythonREPLTool migrated to use AbstractTool framework with matplotlib fixes.
3
+ """
4
+ from typing import Optional, Dict, Any, Union
5
+ import ast
6
+ import sys
7
+ import asyncio
8
+ import threading
9
+ import contextlib
10
+ import base64
11
+ import logging
12
+
13
+ logging.getLogger(name='matplotlib').setLevel(logging.INFO)
14
+
15
+ from pathlib import Path
16
+ from contextlib import redirect_stdout
17
+ from io import StringIO, BytesIO
18
+ from concurrent.futures import ProcessPoolExecutor
19
+ import pandas as pd
20
+ import numpy as np
21
+ import matplotlib
22
+ # Force matplotlib to use non-interactive backend
23
+ matplotlib.use('Agg')
24
+ import matplotlib.pyplot as plt
25
+ # Import these for proper cleanup handling
26
+ from matplotlib import _pylab_helpers
27
+
28
+ from pydantic import BaseModel, Field
29
+ from datamodel.parsers.json import json_decoder, json_encoder # noqa pylint: disable=E0611
30
+ from navconfig import BASE_DIR
31
+ from .abstract import AbstractTool
32
+
33
+
34
+ def brace_escape(text: str) -> str:
35
+ """Escape curly braces in text for format strings."""
36
+ return text.replace('{', '{{').replace('}', '}}')
37
+
38
+ def sanitize_input(query: str) -> str:
39
+ """
40
+ Sanitize input to the python REPL.
41
+ Remove whitespace, backtick & python (if llm mistakes python console as terminal)
42
+
43
+ Args:
44
+ query: The query to sanitize
45
+
46
+ Returns:
47
+ The sanitized query
48
+ """
49
+ query = query.strip()
50
+
51
+ # Handle code blocks
52
+ if query.startswith("```python"):
53
+ query = query[9:]
54
+ elif query.startswith("```"):
55
+ query = query[3:]
56
+
57
+ if query.endswith("```"):
58
+ query = query[:-3]
59
+
60
+ # Clean up any remaining leading/trailing whitespace
61
+ query = query.strip()
62
+
63
+ # Handle common formatting issues
64
+ lines = query.split('\n')
65
+ # Remove empty lines at start and end
66
+ while lines and not lines[0].strip():
67
+ lines.pop(0)
68
+ while lines and not lines[-1].strip():
69
+ lines.pop()
70
+
71
+ return '\n'.join(lines)
72
+
73
+
74
+ class PythonREPLArgs(BaseModel):
75
+ """Arguments schema for PythonREPLTool."""
76
+ code: str = Field(
77
+ description="Python code to execute in the REPL environment"
78
+ )
79
+ debug: bool = False
80
+
81
+
82
+ class PythonREPLTool(AbstractTool):
83
+ """
84
+ Python REPL Tool with pre-loaded data science libraries and enhanced capabilities.
85
+
86
+ Features:
87
+ - Pre-loaded libraries: pandas (pd), numpy (np), matplotlib.pyplot (plt), seaborn (sns), numexpr (ne)
88
+ - Helper functions from parrot.bots.tools under `parrot_tools`
89
+ - An `execution_results` dict for capturing intermediate results
90
+ - A `report_directory` Path for saving outputs
91
+ - Extended JSON encoder/decoder based on orjson (`extended_json`)
92
+ - Async execution support
93
+ - Error handling and sanitization
94
+ - Non-interactive matplotlib backend to avoid GUI issues
95
+ """
96
+
97
+ name = "python_repl"
98
+ description = "Execute Python code with pre-loaded data science libraries (pandas, numpy, matplotlib, seaborn)"
99
+ args_schema = PythonREPLArgs
100
+
101
+ # Class variable to track if environment has been bootstrapped
102
+ _bootstrapped = False
103
+
104
+ def __init__(
105
+ self,
106
+ locals_dict: Optional[Dict] = None,
107
+ globals_dict: Optional[Dict] = None,
108
+ report_dir: Optional[Path] = None,
109
+ plt_style: str = 'seaborn-v0_8-whitegrid',
110
+ palette: str = 'Set2',
111
+ setup_code: Optional[str] = None,
112
+ sanitize_input_enabled: bool = True,
113
+ auto_save_plots: bool = True,
114
+ return_plot_as_base64: bool = False,
115
+ debug: bool = False,
116
+ **kwargs
117
+ ):
118
+ """
119
+ Initialize the Python REPL tool.
120
+
121
+ Args:
122
+ locals_dict: Local variables for the REPL
123
+ globals_dict: Global variables for the REPL
124
+ report_dir: Directory for saving reports
125
+ plt_style: Matplotlib style
126
+ palette: Seaborn color palette
127
+ setup_code: Custom setup code to run
128
+ sanitize_input_enabled: Whether to sanitize input
129
+ auto_save_plots: Whether to automatically save plots to files
130
+ return_plot_as_base64: Whether to return plots as base64 strings
131
+ **kwargs: Additional arguments for AbstractTool
132
+ """
133
+ # Check Python version
134
+ if sys.version_info < (3, 9):
135
+ raise ValueError(
136
+ "This tool requires Python 3.9 or higher "
137
+ f"(you have Python version: {sys.version})"
138
+ )
139
+
140
+ # Set default output directory for reports
141
+ if not report_dir:
142
+ report_dir = BASE_DIR.joinpath('static', 'reports')
143
+
144
+ # Initialize parent class
145
+ super().__init__(output_dir=report_dir, **kwargs)
146
+
147
+ # Configuration
148
+ self.sanitize_input_enabled = sanitize_input_enabled
149
+ self.plt_style = plt_style
150
+ self.palette = palette
151
+ self.setup_code = setup_code or self._get_default_setup_code()
152
+ self.auto_save_plots = auto_save_plots
153
+ self.return_plot_as_base64 = return_plot_as_base64
154
+ # Add a process pool executor
155
+ self.executor = ProcessPoolExecutor(max_workers=4)
156
+
157
+ # Initialize execution environment
158
+ self.locals = locals_dict or {}
159
+ self.globals = globals_dict or {}
160
+
161
+ # Setup matplotlib to use non-interactive backend
162
+ self._setup_charts()
163
+
164
+ # Setup the environment
165
+ self._setup_environment()
166
+
167
+ # Debug:
168
+ self.debug = debug
169
+
170
+ # Bootstrap the environment if not already done
171
+ self._bootstrap()
172
+
173
+ def _setup_charts(self):
174
+ """Configure matplotlib, Altair, and Bokeh for non-interactive use."""
175
+ # Bokeh configuration:
176
+
177
+ # Store the original backend
178
+ original_backend = matplotlib.get_backend()
179
+ with contextlib.suppress(Exception):
180
+ # Force non-interactive backend
181
+ matplotlib.use('Agg', force=True)
182
+
183
+ # Configure matplotlib to not try to show plots
184
+ plt.ioff() # Turn off interactive mode
185
+
186
+ # Clear any existing figures safely
187
+ self._safe_close_all_plots()
188
+
189
+ # Clear any existing figures
190
+ plt.close('all')
191
+
192
+ self.logger.info(f"Matplotlib backend set to: {matplotlib.get_backend()}")
193
+
194
+ def _safe_close_all_plots(self):
195
+ """Safely close all matplotlib plots without GUI errors."""
196
+ try:
197
+ # Get all figure managers
198
+ fignums = list(plt.get_fignums())
199
+ for fignum in fignums:
200
+ try:
201
+ plt.close(fignum)
202
+ except Exception as e:
203
+ self.logger.debug(f"Error closing figure {fignum}: {e}")
204
+
205
+ # Force garbage collection of any remaining figures
206
+ plt.close('all')
207
+
208
+ except Exception as e:
209
+ self.logger.debug(f"Error in safe_close_all_plots: {e}")
210
+
211
+ def _safe_matplotlib_cleanup(self):
212
+ """Safe cleanup of matplotlib figures that won't crash."""
213
+ try:
214
+ # Only cleanup if we're in the main thread
215
+ if threading.current_thread() is threading.main_thread():
216
+ self._safe_close_all_plots()
217
+
218
+ # Clear the figure manager registry safely
219
+ if hasattr(_pylab_helpers, 'Gcf'):
220
+ try:
221
+ _pylab_helpers.Gcf.figs.clear()
222
+ except Exception as e:
223
+ self.logger.debug(f"Error clearing Gcf registry: {e}")
224
+
225
+ except Exception as e:
226
+ # Never let cleanup crash the program
227
+ self.logger.debug(f"Error in safe matplotlib cleanup: {e}")
228
+
229
+ def _execute_function(self, func, *args, **kwargs):
230
+ """Execute a function with proper error isolation to prevent crashes."""
231
+ try:
232
+ return func(*args, **kwargs)
233
+ except (SystemExit, KeyboardInterrupt):
234
+ # Don't catch KeyboardInterrupt as users should be able to interrupt
235
+ raise
236
+ except Exception as e:
237
+ # Log the error but don't let it crash the system
238
+ self.logger.error(f"Error in {func.__name__}: {e}", exc_info=True)
239
+ return f"Error: {type(e).__name__}: {str(e)}"
240
+
241
+ def _default_output_dir(self) -> Path:
242
+ """Get the default output directory for Python REPL outputs."""
243
+ return self.static_dir / "reports" / "python_repl"
244
+
245
+ def _setup_environment(self) -> None:
246
+ """Set up the Python environment with libraries and tools."""
247
+ # Ensure output directory exists
248
+ if not self.output_dir.exists():
249
+ self.output_dir.mkdir(parents=True, exist_ok=True)
250
+
251
+ # Lazy load heavy dependencies
252
+ try:
253
+ import altair
254
+ import plotly.express as px
255
+ import plotly.graph_objects as go
256
+ import plotly.io as pio
257
+ import numexpr as ne
258
+ import seaborn as sns
259
+ import bokeh
260
+ import holoviews as hv
261
+ import folium
262
+ from holoviews import opts
263
+
264
+ # Configure holoviews
265
+ hv.extension('bokeh')
266
+ except ImportError as e:
267
+ self.logger.warning(f"Could not import some data science libraries: {e}")
268
+ # Define dummies or handle gracefully if desired
269
+ # For now, we'll just let them be missing from locals if import failed
270
+ pass
271
+
272
+
273
+ # Helper functions for plot handling
274
+ def save_current_plot(
275
+ filename: Optional[str] = None,
276
+ format: str = 'png',
277
+ dpi: int = 300,
278
+ bbox_inches: str = 'tight'
279
+ ) -> Dict[str, Any]:
280
+ """Save the current matplotlib plot to a file."""
281
+ if not filename:
282
+ filename = self.generate_filename("plot", f".{format}")
283
+
284
+ file_path = self.output_dir / filename
285
+
286
+ try:
287
+ plt.savefig(file_path, format=format, dpi=dpi, bbox_inches=bbox_inches)
288
+ file_url = self.to_static_url(file_path)
289
+
290
+ result = {
291
+ "filename": filename,
292
+ "file_path": str(file_path),
293
+ "file_url": file_url,
294
+ "format": format,
295
+ "dpi": dpi
296
+ }
297
+
298
+ # Optionally add base64 representation
299
+ if self.return_plot_as_base64:
300
+ with open(file_path, 'rb') as f:
301
+ encoded_string = base64.b64encode(f.read()).decode('utf-8')
302
+ result["base64"] = f"data:image/{format};base64,{encoded_string}"
303
+
304
+ return result
305
+
306
+ except Exception as e:
307
+ self.logger.error(f"Error saving plot: {e}")
308
+ return {"error": str(e)}
309
+
310
+ def get_plot_as_base64(format: str = 'png', dpi: int = 300) -> str:
311
+ """Get the current matplotlib plot as a base64 string."""
312
+ try:
313
+ buffer = BytesIO()
314
+ plt.savefig(buffer, format=format, dpi=dpi, bbox_inches='tight')
315
+ buffer.seek(0)
316
+ encoded_string = base64.b64encode(buffer.read()).decode('utf-8')
317
+ buffer.close()
318
+ return f"data:image/{format};base64,{encoded_string}"
319
+ except Exception as e:
320
+ self.logger.error(f"Error getting plot as base64: {e}")
321
+ return f"Error: {str(e)}"
322
+
323
+ def clear_plots():
324
+ """Clear all matplotlib plots."""
325
+ try:
326
+ self._safe_close_all_plots()
327
+ return "All plots cleared"
328
+ except Exception as e:
329
+ self.logger.error(f"Error clearing plots: {e}")
330
+ return f"Error clearing plots: {str(e)}"
331
+
332
+ # Update locals with essential libraries and tools
333
+ self.locals.update({
334
+ # Core data science libraries
335
+ 'pd': pd,
336
+ 'np': np,
337
+ 'plt': plt,
338
+ 'matplotlib': matplotlib,
339
+ 'altair': altair,
340
+ 'px': px,
341
+ 'go': go,
342
+ 'pio': pio,
343
+ 'numexpr': ne,
344
+ 'sns': sns,
345
+ 'bokeh': bokeh,
346
+ 'hv': hv,
347
+ 'opts': opts,
348
+ 'folium': folium,
349
+
350
+ # JSON utilities
351
+ 'json_encoder': json_encoder,
352
+ 'json_decoder': json_decoder,
353
+ 'extended_json': {
354
+ 'dumps': json_encoder,
355
+ 'loads': json_decoder,
356
+ },
357
+
358
+ # Directory and results management
359
+ 'report_directory': self.output_dir,
360
+ 'execution_results': {},
361
+
362
+ # Plot utilities
363
+ 'save_current_plot': save_current_plot,
364
+ 'get_plot_as_base64': get_plot_as_base64,
365
+ 'clear_plots': clear_plots,
366
+ 'execute_safely': lambda code: self.execute_code_safely(code),
367
+
368
+ })
369
+
370
+ # Mirror locals into globals so user code can see everything
371
+ self.globals.update(self.locals)
372
+
373
+ self.logger.info(
374
+ f"Python REPL environment setup complete. Output dir: {self.output_dir}"
375
+ )
376
+
377
+ def _get_default_setup_code(self) -> str:
378
+ """Get the default setup code."""
379
+ return f"""
380
+ # Python REPL Environment Setup
381
+ import warnings
382
+ warnings.filterwarnings('ignore')
383
+
384
+ # Ensure matplotlib uses non-interactive backend
385
+ import matplotlib
386
+ matplotlib.use('Agg', force=True)
387
+ plt.ioff() # Turn off interactive mode
388
+
389
+ # Ensure essential libraries are imported
390
+ try:
391
+ # Uncomment when parrot.bots.tools is available
392
+ # from parrot.bots.tools import (
393
+ # quick_eda,
394
+ # generate_eda_report,
395
+ # list_available_dataframes,
396
+ # create_plot,
397
+ # generate_pdf_from_html
398
+ # )
399
+ pass
400
+ except ImportError as e:
401
+ print(f"Note: Some parrot tools not available: {{e}}")
402
+
403
+ print(f"🐍 Python REPL Environment Ready!")
404
+ print(f"📊 Pandas version: {{pd.__version__}}")
405
+ print(f"🔢 NumPy version: {{np.__version__}}")
406
+ print(f"📈 Matplotlib version: {{matplotlib.__version__}} (backend: {{matplotlib.get_backend()}})")
407
+ print(f"🎨 Seaborn version: {{sns.__version__}}")
408
+ print(f"📁 Report directory: {{report_directory}}")
409
+ print("🖼️ Plot utilities: save_current_plot(), get_plot_as_base64(), clear_plots()")
410
+ print("Use 'execution_results' dict to store intermediate results.")
411
+ """
412
+
413
+ def _bootstrap(self) -> None:
414
+ """Bootstrap the REPL environment."""
415
+ if PythonREPLTool._bootstrapped:
416
+ return
417
+
418
+ self.logger.info("Running REPL bootstrap code...")
419
+ try:
420
+ result = self._execute_code(self.setup_code)
421
+ if result.strip():
422
+ self.logger.info(f"Bootstrap output: {result}")
423
+ except Exception as e:
424
+ self.logger.error("Error during REPL bootstrap", exc_info=e)
425
+
426
+ try:
427
+ plt.style.use(self.plt_style)
428
+ if 'sns' in self.locals:
429
+ self.locals['sns'].set_palette(self.palette)
430
+
431
+ except Exception as e:
432
+ self.logger.error("Error setting plot style", exc_info=e)
433
+
434
+ PythonREPLTool._bootstrapped = True
435
+
436
+ def _auto_save_plots_if_enabled(self) -> Optional[Dict[str, Any]]:
437
+ """Automatically save plots if auto_save_plots is enabled and there are open figures."""
438
+ if not self.auto_save_plots:
439
+ return None
440
+
441
+ # Check if there are any open figures
442
+ if len(plt.get_fignums()) == 0:
443
+ return None
444
+
445
+ try:
446
+ # Save the current plot
447
+ if (save_func := self.locals.get('save_current_plot')):
448
+ result = save_func()
449
+ # Clear the plot after saving to prevent memory issues
450
+ plt.close('all')
451
+ return result
452
+ except Exception as e:
453
+ self.logger.error(f"Error auto-saving plot: {e}")
454
+
455
+ return None
456
+
457
+ def _serialize_execution_results(self, results: Dict[str, Any]) -> Dict[str, Any]:
458
+ """
459
+ Serialize execution results to make them JSON-compatible.
460
+
461
+ Args:
462
+ results: Dictionary of execution results
463
+
464
+ Returns:
465
+ JSON-serializable dictionary
466
+ """
467
+ serializable = {}
468
+
469
+ for key, value in results.items():
470
+ # Ensure key is a string
471
+ str_key = str(key)
472
+
473
+ try:
474
+ # Try to serialize different types of objects
475
+ if isinstance(value, pd.DataFrame):
476
+ serializable[str_key] = {
477
+ "_type": "pandas.DataFrame",
478
+ "data": value.to_dict(orient='records'),
479
+ "columns": list(value.columns),
480
+ "index": list(value.index),
481
+ "shape": value.shape,
482
+ "dtypes": {col: str(dtype) for col, dtype in value.dtypes.items()}
483
+ }
484
+ elif isinstance(value, pd.Series):
485
+ serializable[str_key] = {
486
+ "_type": "pandas.Series",
487
+ "data": value.to_dict(),
488
+ "name": value.name,
489
+ "dtype": str(value.dtype),
490
+ "shape": value.shape
491
+ }
492
+ elif isinstance(value, np.ndarray):
493
+ serializable[str_key] = {
494
+ "_type": "numpy.ndarray",
495
+ "data": value.tolist(),
496
+ "shape": value.shape,
497
+ "dtype": str(value.dtype)
498
+ }
499
+ elif hasattr(value, '__dict__') and not callable(value):
500
+ # For custom objects, try to serialize their __dict__
501
+ serializable[str_key] = {
502
+ "_type": f"{value.__class__.__module__}.{value.__class__.__name__}",
503
+ "data": str(value), # fallback to string representation
504
+ "attributes": {k: str(v) for k, v in value.__dict__.items() if not k.startswith('_')}
505
+ }
506
+ elif callable(value):
507
+ # For functions or callable objects
508
+ serializable[str_key] = {
509
+ "_type": "callable",
510
+ "name": getattr(value, '__name__', str(value)),
511
+ "data": str(value)
512
+ }
513
+ else:
514
+ # Try direct serialization for basic types
515
+ # Test if it's JSON serializable
516
+ json_encoder(value)
517
+ serializable[str_key] = value
518
+
519
+ except Exception as e:
520
+ # If all else fails, store as string representation
521
+ self.logger.warning(f"Could not serialize execution result '{str_key}': {e}")
522
+ serializable[str_key] = {
523
+ "_type": "string_representation",
524
+ "data": str(value),
525
+ "original_type": str(type(value)),
526
+ "serialization_error": str(e)
527
+ }
528
+
529
+ return serializable
530
+
531
+ def _execute_code(self, query: str, debug: bool = False) -> str:
532
+ """Execute Python code and return the result."""
533
+ try:
534
+ if self.sanitize_input_enabled:
535
+ query = sanitize_input(query)
536
+
537
+ # capture previous state of locals
538
+ pre_exec_keys = set(self.locals.keys())
539
+
540
+ if debug:
541
+ print(f"DEBUG: Executing code:\n{repr(query)}\n" + "="*50)
542
+
543
+ if not query.strip():
544
+ return ""
545
+
546
+ # Parse the query
547
+ try:
548
+ tree = ast.parse(query)
549
+ except SyntaxError as e:
550
+ if debug:
551
+ print(f"DEBUG: SyntaxError details: {e}")
552
+ print(f"DEBUG: Query lines: {query.split(chr(10))}")
553
+ return f"SyntaxError: {str(e)}"
554
+
555
+ # If empty, return
556
+ if not tree.body:
557
+ return ""
558
+
559
+ # ✅ CREATE BUFFER - before any execution
560
+ io_buffer = StringIO()
561
+
562
+ with redirect_stdout(io_buffer):
563
+ # Execute all but the last statement
564
+ if len(tree.body) > 1:
565
+ try:
566
+ module = ast.Module(tree.body[:-1], type_ignores=[])
567
+ exec(ast.unparse(module), self.globals, self.locals)
568
+ except Exception as e:
569
+ return f"ExecutionError: {type(e).__name__}: {str(e)}"
570
+
571
+ # Handle the last statement
572
+ last_statement = tree.body[-1]
573
+ module_end = ast.Module([last_statement], type_ignores=[])
574
+ module_end_str = ast.unparse(module_end)
575
+
576
+ # Check if it's an expression that can be evaluated
577
+ if is_expression := isinstance(last_statement, ast.Expr):
578
+ with contextlib.suppress(Exception):
579
+ # Try to evaluate as expression first
580
+ ret = eval(module_end_str, self.globals, self.locals)
581
+ output = io_buffer.getvalue()
582
+
583
+ # Auto-save plots if enabled
584
+ plot_info = self._auto_save_plots_if_enabled()
585
+ if plot_info and not plot_info.get('error'):
586
+ plot_msg = f"\n[Plot saved: {plot_info.get('filename', 'unknown')}]"
587
+ output += plot_msg
588
+
589
+ if ret is None:
590
+ return output
591
+ else:
592
+ return output + str(ret) if output else str(ret)
593
+
594
+ try:
595
+ # Try to evaluate as expression first
596
+ ret = eval(module_end_str, self.globals, self.locals)
597
+
598
+ # Auto-save plots if enabled
599
+ plot_info = self._auto_save_plots_if_enabled()
600
+ if plot_info and not plot_info.get('error'):
601
+ plot_msg = f"\n[Plot saved: {plot_info.get('filename', 'unknown')}]"
602
+ io_buffer.write(plot_msg)
603
+
604
+ if ret is None:
605
+ return io_buffer.getvalue()
606
+ else:
607
+ output = io_buffer.getvalue()
608
+ return output + str(ret) if output else str(ret)
609
+ except Exception:
610
+ # Fall back to execution
611
+ try:
612
+ exec(module_end_str, self.globals, self.locals)
613
+
614
+ # Auto-save plots if enabled
615
+ plot_info = self._auto_save_plots_if_enabled()
616
+ if plot_info and not plot_info.get('error'):
617
+ plot_msg = f"\n[Plot saved: {plot_info.get('filename', 'unknown')}]"
618
+ io_buffer.write(plot_msg)
619
+
620
+ return io_buffer.getvalue()
621
+ except Exception as e:
622
+ return f"ExecutionError: {type(e).__name__}: {str(e)}"
623
+
624
+ # Return everything that was captured
625
+ output = io_buffer.getvalue() or ""
626
+ post_exec_keys = set(self.locals.keys())
627
+
628
+ if new_vars := post_exec_keys - pre_exec_keys:
629
+ context_report = []
630
+ # generate new report context:
631
+ for var_name in new_vars:
632
+ val = self.locals[var_name]
633
+ if "pandas" in str(type(val)) and hasattr(val, "shape"):
634
+ context_report.append(
635
+ f"🆕 DataFrame Created: '{var_name}' | Shape: {val.shape} | Columns: {list(val.columns)}"
636
+ )
637
+ elif not var_name.startswith("_"):
638
+ context_report.append(
639
+ f"🆕 Variable Created: '{var_name}' | Type: {type(val).__name__}"
640
+ )
641
+ else:
642
+ context_report.append(
643
+ f"🆕 Variable Created: '{var_name}' | Type: {type(val).__name__} (private)"
644
+ )
645
+
646
+ return output + "\n".join(context_report)
647
+ return output
648
+
649
+ except Exception as e:
650
+ return f"{type(e).__name__}: {str(e)}"
651
+
652
+ async def _execute(self, code: str, debug: bool = False, **kwargs) -> Dict[str, Any]:
653
+ """
654
+ Execute Python code asynchronously (AbstractTool interface).
655
+
656
+ Args:
657
+ code: Python code to execute
658
+ debug: Enable debug mode
659
+ **kwargs: Additional arguments
660
+
661
+ Returns:
662
+ Dictionary with execution results
663
+ """
664
+ try:
665
+ self.logger.info(f"Executing Python code: {code[:100]}...")
666
+
667
+ # Execute the code in a thread to avoid blocking
668
+ loop = asyncio.get_event_loop()
669
+ return await loop.run_in_executor(
670
+ None,
671
+ self._execute_code,
672
+ code,
673
+ debug
674
+ )
675
+
676
+ except Exception as e:
677
+ self.logger.error(f"Error executing Python code: {e}")
678
+ return {
679
+ "output": f"ToolError: {type(e).__name__}: {str(e)}",
680
+ "code_executed": code,
681
+ "debug_mode": debug,
682
+ "execution_successful": False,
683
+ "error_details": str(e)
684
+ }
685
+
686
+ def execute_sync(self, code: str, debug: bool = False) -> str:
687
+ """
688
+ Execute Python code synchronously.
689
+
690
+ Args:
691
+ code: Python code to execute
692
+ debug: Enable debug mode
693
+
694
+ Returns:
695
+ Execution result as string
696
+ """
697
+ return self._execute_code(code, debug)
698
+
699
+ def get_environment_info(self) -> Dict[str, Any]:
700
+ """Get information about the current REPL environment."""
701
+ return {
702
+ "python_version": sys.version,
703
+ "pandas_version": pd.__version__,
704
+ "numpy_version": np.__version__,
705
+ "matplotlib_version": matplotlib.__version__,
706
+ "matplotlib_backend": matplotlib.get_backend(),
707
+ "seaborn_version": sns.__version__,
708
+ "output_directory": str(self.output_dir),
709
+ "locals_count": len(self.locals),
710
+ "globals_count": len(self.globals),
711
+ "execution_results_keys": list(self.locals.get('execution_results', {}).keys()),
712
+ "open_figures": len(plt.get_fignums()),
713
+ "bootstrapped": self._bootstrapped,
714
+ "plot_style": self.plt_style,
715
+ "color_palette": self.palette,
716
+ "auto_save_plots": self.auto_save_plots,
717
+ "return_plot_as_base64": self.return_plot_as_base64
718
+ }
719
+
720
+ def reset_environment(self) -> None:
721
+ """Reset the REPL environment to its initial state."""
722
+ self.logger.info("Resetting Python REPL environment...")
723
+
724
+ # Clear all plots first
725
+ plt.close('all')
726
+
727
+ # Clear execution results
728
+ if 'execution_results' in self.locals:
729
+ self.locals['execution_results'].clear()
730
+
731
+ # Re-setup matplotlib
732
+ self._setup_charts()
733
+
734
+ # Re-setup the environment
735
+ self._setup_environment()
736
+
737
+ # Re-bootstrap
738
+ PythonREPLTool._bootstrapped = False
739
+ self._bootstrap()
740
+
741
+ self.logger.info("Python REPL environment reset complete")
742
+
743
+ def save_execution_results(self, filename: Optional[str] = None) -> Dict[str, Any]:
744
+ """
745
+ Save current execution results to a JSON file.
746
+
747
+ Args:
748
+ filename: Optional filename for the output file
749
+
750
+ Returns:
751
+ Dictionary with file information
752
+ """
753
+ if not filename:
754
+ filename = self.generate_filename("execution_results", ".json")
755
+
756
+ file_path = self.output_dir / filename
757
+ file_path = self.validate_output_path(file_path)
758
+
759
+ # Get execution results
760
+ execution_results = self.locals.get('execution_results', {})
761
+
762
+ # Serialize execution results safely
763
+ serializable_results = self._serialize_execution_results(execution_results)
764
+
765
+ # Save to file
766
+ try:
767
+ with open(file_path, 'w', encoding='utf-8') as f:
768
+ f.write(json_encoder(serializable_results))
769
+
770
+ file_url = self.to_static_url(file_path)
771
+
772
+ return {
773
+ "filename": filename,
774
+ "file_path": str(file_path),
775
+ "file_url": file_url,
776
+ "results_count": len(execution_results),
777
+ "serializable_count": len(serializable_results),
778
+ "saved_at": self.generate_filename("", "", include_timestamp=True)
779
+ }
780
+
781
+ except Exception as e:
782
+ raise ValueError(
783
+ f"Error saving execution results: {e}"
784
+ ) from e
785
+
786
+ def load_execution_results(self, file_path: Union[str, Path]) -> Dict[str, Any]:
787
+ """
788
+ Load execution results from a JSON file.
789
+
790
+ Args:
791
+ file_path: Path to the JSON file
792
+
793
+ Returns:
794
+ Dictionary with loading information
795
+ """
796
+ file_path = Path(file_path)
797
+
798
+ if not file_path.exists():
799
+ raise ValueError(f"File not found: {file_path}")
800
+
801
+ try:
802
+ with open(file_path, 'r', encoding='utf-8') as f:
803
+ serialized_results = json_decoder(f.read())
804
+
805
+ # Deserialize the results
806
+ results = self._deserialize_execution_results(serialized_results)
807
+
808
+ # Update execution results
809
+ self.locals['execution_results'].update(results)
810
+
811
+ return {
812
+ "file_path": str(file_path),
813
+ "results_loaded": len(results),
814
+ "total_results": len(self.locals['execution_results']),
815
+ "loaded_at": self.generate_filename("", "", include_timestamp=True)
816
+ }
817
+
818
+ except Exception as e:
819
+ raise ValueError(
820
+ f"Error loading execution results: {e}"
821
+ ) from e
822
+
823
+ def _deserialize_execution_results(self, serialized_results: Dict[str, Any]) -> Dict[str, Any]:
824
+ """
825
+ Deserialize execution results from JSON-compatible format.
826
+
827
+ Args:
828
+ serialized_results: Dictionary of serialized results
829
+
830
+ Returns:
831
+ Dictionary with deserialized objects where possible
832
+ """
833
+ results = {}
834
+
835
+ for key, value in serialized_results.items():
836
+ try:
837
+ if isinstance(value, dict) and "_type" in value:
838
+ obj_type = value["_type"]
839
+
840
+ if obj_type == "pandas.DataFrame":
841
+ # Reconstruct DataFrame
842
+ df = pd.DataFrame(value["data"])
843
+ if "columns" in value:
844
+ df.columns = value["columns"]
845
+ results[key] = df
846
+
847
+ elif obj_type == "pandas.Series":
848
+ # Reconstruct Series
849
+ series = pd.Series(value["data"])
850
+ if "name" in value:
851
+ series.name = value["name"]
852
+ results[key] = series
853
+
854
+ elif obj_type == "numpy.ndarray":
855
+ # Reconstruct numpy array
856
+ arr = np.array(value["data"])
857
+ if "shape" in value:
858
+ arr = arr.reshape(value["shape"])
859
+ results[key] = arr
860
+
861
+ elif obj_type in ["string_representation", "callable"]:
862
+ # Keep as metadata dict for non-reconstructible objects
863
+ results[key] = value
864
+
865
+ else:
866
+ # For other custom types, keep the serialized representation
867
+ results[key] = value
868
+ else:
869
+ # Direct value
870
+ results[key] = value
871
+
872
+ except Exception as e:
873
+ self.logger.warning(f"Could not deserialize execution result '{key}': {e}")
874
+ # Keep the serialized version
875
+ results[key] = value
876
+
877
+ return results
878
+
879
+ def __call__(self, code: str, debug: bool = False) -> str:
880
+ """Make the tool callable for backward compatibility."""
881
+ return self.execute_sync(code, debug)
882
+
883
+ @contextlib.contextmanager
884
+ def safe_execution_context(self):
885
+ """Context manager for safe code execution that prevents crashes."""
886
+ old_excepthook = sys.excepthook
887
+
888
+ def safe_excepthook(exc_type, exc_value, exc_traceback):
889
+ """Custom exception hook that prevents crashes from matplotlib issues."""
890
+ if (exc_type == RuntimeError and
891
+ 'main thread is not in main loop' in str(exc_value)):
892
+ self.logger.warning("Caught matplotlib threading issue, continuing...")
893
+ return
894
+ elif (exc_type == RuntimeError and 'Calling Tcl from different apartment' in str(exc_value)):
895
+ self.logger.warning("Caught matplotlib Tcl issue, continuing...")
896
+ return
897
+ else:
898
+ # Call the original exception hook for other exceptions
899
+ old_excepthook(exc_type, exc_value, exc_traceback)
900
+
901
+ try:
902
+ sys.excepthook = safe_excepthook
903
+ yield
904
+ finally:
905
+ sys.excepthook = old_excepthook
906
+
907
+ def execute_code_safely(self, code: str, debug: bool = False) -> str:
908
+ """Execute code with maximum safety against crashes."""
909
+ with self.safe_execution_context():
910
+ return self._execute_code(code, debug)