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,1098 @@
1
+ from __future__ import annotations
2
+ from typing import Dict, List, Any, Union, Optional, Callable
3
+ from collections.abc import Generator
4
+ import asyncio
5
+ from dataclasses import dataclass
6
+ import logging
7
+ from enum import Enum
8
+ import pandas as pd
9
+ from .math import MathTool
10
+ from .abstract import AbstractTool, ToolResult
11
+ import aiohttp
12
+ from ..a2a.models import RegisteredAgent, AgentCard
13
+
14
+
15
+ @dataclass
16
+ class ToolDefinition:
17
+ """Data structure for tool definition."""
18
+ """Defines a tool with its name, description, input schema, and function."""
19
+ __slots__ = ('name', 'description', 'input_schema', 'function')
20
+ name: str
21
+ description: str
22
+ input_schema: Dict[str, Any]
23
+ function: Callable
24
+
25
+
26
+ class ToolFormat(Enum):
27
+ """Enum for different tool format requirements by LLM providers."""
28
+ OPENAI = "openai"
29
+ ANTHROPIC = "anthropic"
30
+ GOOGLE = "google"
31
+ GROQ = "groq"
32
+ VERTEX = "vertex"
33
+ GENERIC = "generic"
34
+
35
+
36
+ class ToolSchemaAdapter:
37
+ """
38
+ Adapter class to convert tool schemas between different LLM provider formats.
39
+ """
40
+
41
+ @staticmethod
42
+ def clean_schema_for_provider(
43
+ schema: Dict[str, Any],
44
+ provider: ToolFormat
45
+ ) -> Dict[str, Any]:
46
+ """
47
+ Clean and adapt tool schema for specific LLM provider requirements.
48
+
49
+ Args:
50
+ schema: Original tool schema
51
+ provider: Target LLM provider format
52
+
53
+ Returns:
54
+ Cleaned schema compatible with the provider
55
+ """
56
+ cleaned_schema = schema.copy()
57
+
58
+ # Remove internal metadata
59
+ cleaned_schema.pop('_tool_instance', None)
60
+
61
+ if provider in [ToolFormat.GOOGLE, ToolFormat.VERTEX]:
62
+ # Google/Vertex AI specific cleaning
63
+ return ToolSchemaAdapter._clean_for_google(cleaned_schema)
64
+ elif provider == ToolFormat.GROQ:
65
+ # Groq specific cleaning
66
+ return ToolSchemaAdapter._clean_for_groq(cleaned_schema)
67
+ elif provider in [ToolFormat.OPENAI, ToolFormat.ANTHROPIC]:
68
+ # OpenAI/Anthropic specific cleaning
69
+ return ToolSchemaAdapter._clean_for_openai(cleaned_schema)
70
+ else:
71
+ # Generic cleaning
72
+ return ToolSchemaAdapter._clean_generic(cleaned_schema)
73
+
74
+ @staticmethod
75
+ def _clean_for_google(schema: Dict[str, Any]) -> Dict[str, Any]:
76
+ """Clean schema for Google/Vertex AI compatibility."""
77
+ cleaned = schema.copy()
78
+
79
+ # Remove additionalProperties recursively
80
+ def remove_additional_properties(obj):
81
+ if isinstance(obj, dict):
82
+ # Remove additionalProperties
83
+ obj.pop('additionalProperties', None)
84
+ # Remove other unsupported properties
85
+ obj.pop('title', None) # Google doesn't use title in parameters
86
+
87
+ # Recursively clean nested objects
88
+ for _, value in obj.items():
89
+ remove_additional_properties(value)
90
+ elif isinstance(obj, list):
91
+ for item in obj:
92
+ remove_additional_properties(item)
93
+
94
+ if 'parameters' in cleaned:
95
+ remove_additional_properties(cleaned['parameters'])
96
+
97
+ return cleaned
98
+
99
+ @staticmethod
100
+ def _clean_for_groq(schema: Dict[str, Any]) -> Dict[str, Any]:
101
+ """Clean schema for Groq compatibility."""
102
+ cleaned = schema.copy()
103
+
104
+ def remove_unsupported_constraints(obj):
105
+ if isinstance(obj, dict):
106
+ # Remove validation constraints that Groq doesn't support
107
+ unsupported = [
108
+ "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum",
109
+ "minLength", "maxLength", "pattern", "format",
110
+ "minItems", "maxItems", "uniqueItems",
111
+ "minProperties", "maxProperties"
112
+ ]
113
+
114
+ for constraint in unsupported:
115
+ obj.pop(constraint, None)
116
+
117
+ # Set additionalProperties to false for objects
118
+ if obj.get("type") == "object":
119
+ obj["additionalProperties"] = False
120
+
121
+ # Recursively clean nested objects
122
+ for key, value in obj.items():
123
+ remove_unsupported_constraints(value)
124
+ elif isinstance(obj, list):
125
+ for item in obj:
126
+ remove_unsupported_constraints(item)
127
+
128
+ if 'parameters' in cleaned:
129
+ remove_unsupported_constraints(cleaned['parameters'])
130
+
131
+ return cleaned
132
+
133
+ @staticmethod
134
+ def _clean_for_openai(schema: Dict[str, Any]) -> Dict[str, Any]:
135
+ """Clean schema for OpenAI/Anthropic compatibility."""
136
+ cleaned = schema.copy()
137
+
138
+ def ensure_openai_object(obj):
139
+ if isinstance(obj, dict):
140
+ if obj.get("type") == "object":
141
+ props = obj.get("properties", {}) or {}
142
+
143
+ # Ensure additionalProperties is set
144
+ if "additionalProperties" not in obj:
145
+ obj["additionalProperties"] = False
146
+
147
+ # 🔑 Ensure 'required' exists and includes ALL properties
148
+ prop_keys = list(props.keys())
149
+ if "required" not in obj:
150
+ obj["required"] = prop_keys
151
+ else:
152
+ required = obj.get("required") or []
153
+ missing = [k for k in prop_keys if k not in required]
154
+ obj["required"] = required + missing
155
+
156
+ # Recurse into nested dicts/lists
157
+ for _, value in obj.items():
158
+ ensure_openai_object(value)
159
+
160
+ elif isinstance(obj, list):
161
+ for item in obj:
162
+ ensure_openai_object(item)
163
+
164
+ if 'parameters' in cleaned:
165
+ ensure_openai_object(cleaned['parameters'])
166
+
167
+ return cleaned
168
+
169
+ @staticmethod
170
+ def _clean_generic(schema: Dict[str, Any]) -> Dict[str, Any]:
171
+ """Generic schema cleaning."""
172
+ cleaned = schema.copy()
173
+
174
+ # Remove internal metadata and ensure basic structure
175
+ cleaned.pop('_tool_instance', None)
176
+
177
+ # Ensure required fields exist
178
+ if 'parameters' not in cleaned:
179
+ cleaned['parameters'] = {
180
+ "type": "object",
181
+ "properties": {},
182
+ "required": []
183
+ }
184
+
185
+ return cleaned
186
+
187
+
188
+ class ToolManager:
189
+ """
190
+ Unified tool manager for handling tools across AbstractBot and AbstractClient.
191
+ """
192
+
193
+ def __init__(
194
+ self,
195
+ logger: Optional[logging.Logger] = None,
196
+ debug: bool = False
197
+ ):
198
+ """
199
+ Initialize tool manager.
200
+
201
+ Args:
202
+ logger: Logger instance
203
+ """
204
+ self._shared: Dict[str, Any] = {"dataframes": {}} # name -> (df, meta)
205
+ self._registered_agents: Dict[str, RegisteredAgent] = {}
206
+ self._result_hooks: List[Callable[[str, Any, Dict[str, Any]], None]] = []
207
+ self.logger = logger or logging.getLogger(self.__class__.__name__)
208
+ self._debug: bool = debug
209
+ self._tools: Dict[str, Union[ToolDefinition, AbstractTool]] = {}
210
+ self._categories: Dict[str, List[str]] = {}
211
+ # policy (tweak as required)
212
+ self.auto_share_dataframes: bool = True
213
+ self.auto_push_to_pandas: bool = True
214
+ self.pandas_tool_name: str = "python_pandas"
215
+
216
+ # Self-register the search tool
217
+ self.register_tool(
218
+ name="search_tools",
219
+ description="Search for available tools by name or description. Use this to find tools that can help with your task.",
220
+ input_schema={
221
+ "type": "object",
222
+ "properties": {
223
+ "query": {
224
+ "type": "string",
225
+ "description": "The search query to match against tool names and descriptions"
226
+ },
227
+ "limit": {
228
+ "type": "integer",
229
+ "description": "Maximum number of results to return",
230
+ "default": 15
231
+ }
232
+ },
233
+ "required": ["query"]
234
+ },
235
+ function=self.search_tools
236
+ )
237
+
238
+ def search_tools(self, query: str, limit: int = 15) -> str:
239
+ """
240
+ Search for tools by name or description.
241
+
242
+ Args:
243
+ query: Search query
244
+ limit: Max results
245
+
246
+ Returns:
247
+ JSON string list of matching tools with descriptions
248
+ """
249
+ query = query.lower().strip()
250
+ matches = []
251
+
252
+ for name, tool in self._tools.items():
253
+ if name == "search_tools":
254
+ continue
255
+
256
+ # Get description
257
+ desc = ""
258
+ if hasattr(tool, 'description'):
259
+ desc = tool.description
260
+ elif isinstance(tool, dict):
261
+ desc = tool.get('description', '')
262
+
263
+ # Check match
264
+ if query in name.lower() or query in desc.lower():
265
+ matches.append({
266
+ "name": name,
267
+ "description": desc
268
+ })
269
+
270
+ # Sort by name and limit
271
+ matches.sort(key=lambda x: x['name'])
272
+ matches = matches[:limit]
273
+
274
+ if not matches:
275
+ return f"No tools found matching '{query}'. Try a different search term."
276
+
277
+ import json
278
+ return json.dumps(matches, indent=2)
279
+
280
+ def default_tools(self, tools: list = None) -> List[AbstractTool]:
281
+ if not tools:
282
+ tools = [
283
+ MathTool(),
284
+ ]
285
+ self.register_tools(tools)
286
+
287
+ @property
288
+ def tools(self) -> List[AbstractTool]:
289
+ """Get list of registered tool instances."""
290
+ return self._tools
291
+
292
+ def sync(self, other_manager: 'ToolManager') -> None:
293
+ """
294
+ Sync tools from another ToolManager instance.
295
+
296
+ Args:
297
+ other_manager: Another ToolManager instance to sync from
298
+ """
299
+ if not isinstance(other_manager, ToolManager):
300
+ self.logger.error("Can only sync from another ToolManager instance")
301
+ return
302
+
303
+ for tool_name, tool in other_manager._tools.items():
304
+ if tool_name not in self._tools:
305
+ self._tools[tool_name] = tool
306
+ self.logger.debug(
307
+ f"Synchronized tool: {tool_name}"
308
+ )
309
+ else:
310
+ self.logger.debug(
311
+ f"Tool already exists, skipping: {tool_name}"
312
+ )
313
+
314
+ def add_tool(self, tool: Union[ToolDefinition, AbstractTool], name: Optional[str] = None) -> None:
315
+ """
316
+ Add a tool to the manager.
317
+
318
+ Args:
319
+ tool: Tool instance (AbstractTool or ToolDefinition)
320
+ name: Optional custom name for the tool
321
+ """
322
+ tool_name = name or getattr(tool, 'name', None) or tool.__class__.__name__
323
+ if isinstance(tool, AbstractTool) or isinstance(tool, ToolDefinition):
324
+ self._tools[tool_name] = tool
325
+ self.logger.debug(
326
+ f"Registered tool: {tool_name}"
327
+ )
328
+ else:
329
+ self.logger.error(
330
+ f"Unsupported tool type: {type(tool)}"
331
+ )
332
+
333
+ def register_tool(
334
+ self,
335
+ tool: Union[dict, ToolDefinition, AbstractTool] = None,
336
+ name: str = None,
337
+ description: str = None,
338
+ input_schema: Dict[str, Any] = None,
339
+ function: Callable = None,
340
+ ) -> None:
341
+ """
342
+ Register a tool in the unified format.
343
+
344
+ Args:
345
+ tool: Tool instance (AbstractTool, ToolDefinition, or dict)
346
+ name: Optional custom name for the tool
347
+ """
348
+ tool_name = tool.name if isinstance(tool, (ToolDefinition, AbstractTool)) else name
349
+ if tool_name in self._tools:
350
+ self.logger.warning(
351
+ f"Tool '{tool_name}' is already registered."
352
+ )
353
+ return
354
+ try:
355
+ if isinstance(tool, (ToolDefinition, AbstractTool)):
356
+ self._tools[tool_name] = tool
357
+ elif isinstance(tool, dict):
358
+ tool_name = tool.get('name')
359
+ if tool_name in self._tools:
360
+ self.logger.warning(f"Tool '{tool_name}' is already registered.")
361
+ return
362
+ self._tools[tool_name] = ToolDefinition(
363
+ name=tool_name,
364
+ description=tool.get('description', ''),
365
+ input_schema=tool.get('parameters', {}),
366
+ function=tool.get('_tool_instance')
367
+ )
368
+ elif name and description and input_schema and function:
369
+ # Create a ToolDefinition from the provided parameters
370
+ self._tools[tool_name] = ToolDefinition(
371
+ name=name,
372
+ description=description,
373
+ input_schema=input_schema,
374
+ function=function
375
+ )
376
+ else:
377
+ # TODO: if provided a function and a name, create the input_schema based on instrospection
378
+ if not (name and description and input_schema and function):
379
+ self.logger.error(
380
+ f"Tool '{tool_name}' must be a ToolDefinition, AbstractTool, or provide all parameters: "
381
+ "name, description, input_schema, function."
382
+ )
383
+ raise ValueError(
384
+ "Tool must be a ToolDefinition, AbstractTool, or provide all parameters: "
385
+ "name, description, input_schema, function."
386
+ )
387
+ self.logger.debug(
388
+ f"Registered tool: {tool_name}"
389
+ )
390
+ except Exception as e:
391
+ self.logger.error(
392
+ f"Error registering tool: {e}"
393
+ )
394
+
395
+ def register_tools(
396
+ self,
397
+ tools: List[Union[ToolDefinition, AbstractTool]]
398
+ ) -> None:
399
+ """
400
+ Register multiple tools from list or dictionary.
401
+
402
+ Args:
403
+ tools: List of tools or dictionary of tools
404
+ """
405
+ if not tools:
406
+ return
407
+ for tool in tools:
408
+ if isinstance(tool, str):
409
+ # If tool is a string, load it by name
410
+ self.load_tool(tool)
411
+ elif isinstance(tool, AbstractTool):
412
+ # Register AbstractTool instance directly
413
+ self.register_tool(tool)
414
+ elif isinstance(tool, ToolDefinition):
415
+ # Register ToolDefinition instance directly
416
+ self.register_tool(tool, tool.name)
417
+ elif isinstance(tool, dict):
418
+ # Register dictionary as a tool
419
+ self.register_tool(tool)
420
+ elif hasattr(tool, 'name'):
421
+ self.register_tool(tool, tool.name)
422
+ else:
423
+ self.logger.error(
424
+ f"Unsupported tool type: {type(tool)}"
425
+ )
426
+
427
+ def load_tool(self, tool_name: str, **kwargs) -> bool:
428
+ """
429
+ Load a tool by name.
430
+
431
+ Args:
432
+ tool_name: Name of the tool to load
433
+
434
+ Returns:
435
+ Tool instance or None if not found
436
+ """
437
+ if tool_name in self._tools:
438
+ return self._tools[tool_name]
439
+
440
+ tool_file = tool_name.lower().replace('tool', '')
441
+ try:
442
+ module = __import__(f"parrot.tools.{tool_file}", fromlist=[tool_name])
443
+ cls = getattr(module, tool_name)
444
+ self._tools[tool_name] = cls(**kwargs)
445
+ return True
446
+ except (ImportError, AttributeError) as e:
447
+ self.logger.error(
448
+ f"Error loading tool {tool_name}: {e}"
449
+ )
450
+ return False
451
+
452
+ def get_tool_schemas(
453
+ self,
454
+ provider_format: ToolFormat = ToolFormat.GENERIC
455
+ ) -> List[Dict[str, Any]]:
456
+ """
457
+ Get tool schemas formatted for specific LLM provider.
458
+
459
+ Args:
460
+ provider_format: Target provider format
461
+
462
+ Returns:
463
+ List of tool schemas compatible with the provider
464
+ """
465
+ if not self._tools:
466
+ return []
467
+
468
+ client_tools = []
469
+
470
+ for tool_name, tool in self._tools.items():
471
+ try:
472
+ # Get tool schema
473
+ schema = self._extract_tool_schema(tool, tool_name)
474
+
475
+ if schema:
476
+ # Add tool instance reference for execution
477
+ schema['_tool_instance'] = tool
478
+ # Clean schema for provider compatibility
479
+ cleaned_schema = ToolSchemaAdapter.clean_schema_for_provider(
480
+ schema, provider_format
481
+ )
482
+ # Re-add tool instance after cleaning
483
+ cleaned_schema['_tool_instance'] = tool
484
+ client_tools.append(cleaned_schema)
485
+
486
+ except Exception as e:
487
+ self.logger.error(f"Error preparing tool {tool_name}: {e}")
488
+
489
+ return client_tools
490
+
491
+ def _extract_tool_schema(self, tool: Any, tool_name: str) -> Optional[Dict[str, Any]]:
492
+ """
493
+ Extract schema from various tool formats.
494
+
495
+ Args:
496
+ tool: Tool instance
497
+ tool_name: Tool name
498
+
499
+ Returns:
500
+ Tool schema dictionary or None
501
+ """
502
+ try:
503
+ # AbstractTool with get_tool_schema method
504
+ if hasattr(tool, 'get_tool_schema'):
505
+ return tool.get_tool_schema()
506
+
507
+ # ToolDefinition with input_schema
508
+ elif hasattr(tool, 'input_schema') and hasattr(tool, 'description'):
509
+ return {
510
+ "name": tool_name,
511
+ "description": tool.description,
512
+ "parameters": tool.input_schema
513
+ }
514
+
515
+ # Dictionary format
516
+ elif isinstance(tool, dict):
517
+ if 'name' in tool and 'parameters' in tool:
518
+ return tool
519
+ else:
520
+ # Try to construct from available fields
521
+ return {
522
+ "name": tool.get('name', tool_name),
523
+ "description": tool.get('description', f"Tool: {tool_name}"),
524
+ "parameters": tool.get('parameters', tool.get('input_schema', {}))
525
+ }
526
+
527
+ # Legacy format with name, description, input_schema attributes
528
+ elif hasattr(tool, 'name') and hasattr(tool, 'description'):
529
+ schema = getattr(tool, 'input_schema', {})
530
+ return {
531
+ "name": tool.name,
532
+ "description": tool.description,
533
+ "parameters": schema
534
+ }
535
+
536
+ else:
537
+ self.logger.warning(f"Unknown tool format for: {tool_name}")
538
+ return None
539
+
540
+ except Exception as e:
541
+ self.logger.error(
542
+ f"Error extracting schema for {tool_name}: {e}"
543
+ )
544
+ return None
545
+
546
+ def get_tool(self, tool_name: str) -> Optional[Any]:
547
+ """
548
+ Get tool instance by name.
549
+
550
+ Args:
551
+ tool_name: Name of the tool
552
+
553
+ Returns:
554
+ Tool instance or None
555
+ """
556
+ return self._tools.get(tool_name)
557
+
558
+ def list_categories(self) -> List[str]:
559
+ """List available tool categories."""
560
+ return list(self._categories.keys())
561
+
562
+ def get_tools_by_category(self, category: str) -> List[str]:
563
+ """Get tools by category."""
564
+ return self._categories.get(category, [])
565
+
566
+ def list_tools(self) -> List[str]:
567
+ """Get list of registered tool names."""
568
+ return list(self._tools.keys())
569
+
570
+ def get_tools(self) -> Dict[str, Any]:
571
+ """Get all registered tools."""
572
+ return self._tools.values()
573
+
574
+ def get_all_tools(self) -> List[Union[ToolDefinition, AbstractTool]]:
575
+ """Get all registered tool instances."""
576
+ return list(self._tools.values())
577
+
578
+ def all_tools(self) -> Generator[Any, Any, Any]:
579
+ """
580
+ Get all registered tools with their schemas as a generator.
581
+
582
+ Returns:
583
+ List of tool schemas
584
+ """
585
+ for tool in self._tools.values():
586
+ yield tool
587
+
588
+ def unregister_tool(self, tool_name: str) -> bool:
589
+ """Unregister a tool by name."""
590
+ if tool_name in self._tools:
591
+ del self._tools[tool_name]
592
+ return True
593
+ return False
594
+
595
+ def clear_tools(self) -> None:
596
+ """Clear all registered tools."""
597
+ self._tools.clear()
598
+ self._categories.clear()
599
+ self.logger.debug("Cleared all tools")
600
+
601
+ def remove_tool(self, tool_name: str) -> None:
602
+ """
603
+ Remove a tool by name.
604
+
605
+ Args:
606
+ tool_name: Name of the tool to remove
607
+ """
608
+ if tool_name in self._tools:
609
+ del self._tools[tool_name]
610
+ self.logger.debug(
611
+ f"Removed tool: {tool_name}"
612
+ )
613
+ else:
614
+ self.logger.warning(f"Tool not found: {tool_name}")
615
+
616
+ def __repr__(self) -> str:
617
+ """String representation of the ToolManager."""
618
+ return f"ToolManager(tools={list(self._tools.keys())})"
619
+
620
+ def __len__(self) -> int:
621
+ """Get number of registered tools."""
622
+ return len(self._tools)
623
+
624
+ def build_tools_description(
625
+ self,
626
+ format_style: str = "compact",
627
+ include_parameters: bool = True,
628
+ include_examples: bool = False,
629
+ max_tools: Optional[int] = None
630
+ ) -> str:
631
+ """
632
+ Build formatted tool descriptions for system prompts.
633
+
634
+ Args:
635
+ format_style: Style of formatting ("detailed", "compact", "list", "markdown")
636
+ include_parameters: Whether to include parameter details
637
+ include_examples: Whether to include usage examples
638
+ max_tools: Maximum number of tools to include (None for all)
639
+
640
+ Returns:
641
+ Formatted string describing all available tools
642
+ """
643
+ if not self._tools:
644
+ return "No tools available."
645
+
646
+ # Get tools to describe (limit if specified)
647
+ tools_to_describe = list(self._tools.items())
648
+ if max_tools:
649
+ tools_to_describe = tools_to_describe[:max_tools]
650
+
651
+ if format_style == "detailed":
652
+ return self._build_detailed_description(
653
+ tools_to_describe,
654
+ include_parameters,
655
+ include_examples
656
+ )
657
+ elif format_style == "compact":
658
+ return self._build_compact_description(tools_to_describe, include_parameters)
659
+ elif format_style == "list":
660
+ return self._build_list_description(tools_to_describe)
661
+ elif format_style == "markdown":
662
+ return self._build_markdown_description(
663
+ tools_to_describe,
664
+ include_parameters,
665
+ include_examples
666
+ )
667
+ else:
668
+ return self._build_detailed_description(
669
+ tools_to_describe,
670
+ include_parameters,
671
+ include_examples
672
+ )
673
+
674
+ def _build_detailed_description(
675
+ self,
676
+ tools: List[tuple],
677
+ include_parameters: bool,
678
+ include_examples: bool
679
+ ) -> str:
680
+ """Build detailed tool descriptions."""
681
+ descriptions = ["=== AVAILABLE TOOLS ===\n"]
682
+
683
+ for i, (tool_name, tool) in enumerate(tools, 1):
684
+ try:
685
+ schema = self._extract_tool_schema(tool, tool_name)
686
+ if not schema:
687
+ continue
688
+
689
+ # Tool header
690
+ descriptions.append(f"{i}. {schema['name']}: {schema['description']}")
691
+
692
+ # Parameters section
693
+ if include_parameters and 'parameters' in schema:
694
+ params = schema['parameters'].get('properties', {})
695
+ required = schema['parameters'].get('required', [])
696
+
697
+ if params:
698
+ descriptions.append(" Parameters:")
699
+ for param_name, param_info in params.items():
700
+ param_type = param_info.get('type', 'unknown')
701
+ param_desc = param_info.get('description', 'No description')
702
+ required_marker = " (required)" if param_name in required else " (optional)"
703
+ descriptions.append(f" - {param_name} ({param_type}){required_marker}: {param_desc}")
704
+
705
+ # Usage example
706
+ if include_examples:
707
+ descriptions.append(f" Usage: Call {schema['name']} when you need to {schema['description'].lower()}")
708
+
709
+ descriptions.append("") # Empty line between tools
710
+
711
+ except Exception as e:
712
+ self.logger.error(f"Error building description for {tool_name}: {e}")
713
+ descriptions.append(f"{i}. {tool_name}: Error getting tool information")
714
+ descriptions.append("")
715
+
716
+ descriptions.append(
717
+ "Use these tools when appropriate to answer the question effectively."
718
+ )
719
+ return "\n".join(descriptions)
720
+
721
+ def _build_compact_description(self, tools: List[tuple], include_parameters: bool) -> str:
722
+ """Build compact tool descriptions."""
723
+ descriptions = ["Available tools: "]
724
+ tool_summaries = []
725
+
726
+ for tool_name, tool in tools:
727
+ try:
728
+ schema = self._extract_tool_schema(tool, tool_name)
729
+ if not schema:
730
+ continue
731
+
732
+ summary = f"{schema['name']}"
733
+
734
+ if include_parameters and 'parameters' in schema:
735
+ params = schema['parameters'].get('properties', {})
736
+ if params:
737
+ param_names = list(params.keys())[:3] # First 3 params
738
+ param_str = ", ".join(param_names)
739
+ if len(params) > 3:
740
+ param_str += "..."
741
+ summary += f"({param_str})"
742
+
743
+ summary += f" - {schema['description']}"
744
+ tool_summaries.append(summary)
745
+
746
+ except Exception as e:
747
+ self.logger.error(f"Error building compact description for {tool_name}: {e}")
748
+ tool_summaries.append(f"{tool_name} - Tool information unavailable")
749
+
750
+ descriptions.extend(tool_summaries)
751
+ return "; ".join(descriptions) + "."
752
+
753
+ def _build_list_description(self, tools: List[tuple]) -> str:
754
+ """Build simple list of tool names and descriptions."""
755
+ descriptions = ["Available tools:\n"]
756
+
757
+ for tool_name, tool in tools:
758
+ try:
759
+ schema = self._extract_tool_schema(tool, tool_name)
760
+ if schema:
761
+ descriptions.append(f"• {schema['name']}: {schema['description']}")
762
+ else:
763
+ descriptions.append(f"• {tool_name}: Description unavailable")
764
+ except Exception as e:
765
+ self.logger.error(f"Error building list description for {tool_name}: {e}")
766
+ descriptions.append(f"• {tool_name}: Error getting information")
767
+
768
+ return "\n".join(descriptions)
769
+
770
+ def _build_markdown_description(
771
+ self,
772
+ tools: List[tuple],
773
+ include_parameters: bool,
774
+ include_examples: bool
775
+ ) -> str:
776
+ """Build markdown-formatted tool descriptions."""
777
+ descriptions = ["## Available Tools\n"]
778
+
779
+ for tool_name, tool in tools:
780
+ try:
781
+ schema = self._extract_tool_schema(tool, tool_name)
782
+ if not schema:
783
+ continue
784
+
785
+ # Tool header
786
+ descriptions.append(f"### {schema['name']}")
787
+ descriptions.append(f"**Description:** {schema['description']}\n")
788
+
789
+ # Parameters section
790
+ if include_parameters and 'parameters' in schema:
791
+ params = schema['parameters'].get('properties', {})
792
+ required = schema['parameters'].get('required', [])
793
+
794
+ if params:
795
+ descriptions.append("**Parameters:**")
796
+ for param_name, param_info in params.items():
797
+ param_type = param_info.get('type', 'unknown')
798
+ param_desc = param_info.get('description', 'No description')
799
+ required_marker = "**required**" if param_name in required else "*optional*"
800
+ descriptions.append(f"- `{param_name}` ({param_type}) - {required_marker}: {param_desc}")
801
+ descriptions.append("")
802
+
803
+ # Usage example
804
+ if include_examples:
805
+ descriptions.append(f"**Usage:** Call `{schema['name']}` when you need to {schema['description'].lower()}\n")
806
+
807
+ except Exception as e:
808
+ self.logger.error(f"Error building markdown description for {tool_name}: {e}")
809
+ descriptions.append(f"### {tool_name}\n**Error:** Could not retrieve tool information\n")
810
+
811
+ return "\n".join(descriptions)
812
+
813
+ def get_tools_summary(self) -> Dict[str, Any]:
814
+ """
815
+ Get a summary of all registered tools.
816
+
817
+ Returns:
818
+ Dictionary with tool count and basic information
819
+ """
820
+ if not self._tools:
821
+ return {"count": 0, "tools": []}
822
+
823
+ tools_info = []
824
+ for tool_name, tool in self._tools.items():
825
+ try:
826
+ schema = self._extract_tool_schema(tool, tool_name)
827
+ tool_info = {
828
+ "name": tool_name,
829
+ "description": schema.get(
830
+ 'description', 'No description'
831
+ ) if schema else 'Schema unavailable',
832
+ "parameters_count": len(
833
+ schema.get('parameters', {}).get('properties', {})
834
+ ) if schema else 0
835
+ }
836
+ tools_info.append(tool_info)
837
+ except Exception as e:
838
+ self.logger.error(f"Error getting summary for {tool_name}: {e}")
839
+ tools_info.append({
840
+ "name": tool_name,
841
+ "description": "Error getting information",
842
+ "parameters_count": 0
843
+ })
844
+
845
+ return {
846
+ "count": len(self._tools),
847
+ "tools": tools_info
848
+ }
849
+
850
+ async def execute_tool(
851
+ self,
852
+ tool_name: str,
853
+ parameters: Dict[str, Any]
854
+ ) -> Any:
855
+ """Execute a registered tool function."""
856
+
857
+ if tool_name not in self._tools:
858
+ raise ValueError(
859
+ f"Tool '{tool_name}' not registered"
860
+ )
861
+ try:
862
+ tool = self._tools[tool_name]
863
+ if isinstance(tool, ToolDefinition):
864
+ if asyncio.iscoroutinefunction(tool.function):
865
+ result = await tool.function(**parameters)
866
+ else:
867
+ result = tool.function(**parameters)
868
+
869
+ self.logger.debug(
870
+ f"Executed tool '{tool_name}' with parameters: {parameters}"
871
+ )
872
+ return result
873
+
874
+ elif isinstance(tool, AbstractTool):
875
+ # Handle AbstractTool (new)
876
+ result = await tool.execute(**parameters)
877
+ # Handle ToolResult objects
878
+ if isinstance(result, ToolResult):
879
+ if result.status == "error":
880
+ raise ValueError(result.error)
881
+ out = result.result
882
+ meta = getattr(result, "metadata", {}) or {}
883
+ else:
884
+ out = result
885
+ meta = {}
886
+ self._postprocess_result(tool_name, out, meta)
887
+ self._run_result_hooks(tool_name, out, meta)
888
+ return out
889
+ else:
890
+ raise ValueError(
891
+ f"Unknown tool type: {type(tool)}"
892
+ )
893
+ except Exception as e:
894
+ self.logger.error(
895
+ f"Error executing tool {tool_name}: {e}"
896
+ )
897
+ raise
898
+
899
+ async def register_a2a_agent(self, url: str) -> RegisteredAgent:
900
+ """
901
+ Register an A2A agent by its URL.
902
+
903
+ Args:
904
+ url (str): The base URL of the A2A agent.
905
+
906
+ Returns:
907
+ RegisteredAgent: The registered agent object.
908
+
909
+ Raises:
910
+ Exception: If registration fails or agent is unreachable.
911
+ """
912
+ url = url.rstrip('/')
913
+ agent_url = f"{url}/.well-known/agent.json"
914
+
915
+ try:
916
+ async with aiohttp.ClientSession() as session:
917
+ async with session.get(agent_url) as response:
918
+ if response.status != 200:
919
+ raise ValueError(f"Failed to fetch agent card: {response.status}")
920
+
921
+ data = await response.json()
922
+ card = AgentCard.from_dict(data)
923
+ card.url = url # Ensure URL is set to the base URL
924
+
925
+ agent = RegisteredAgent(
926
+ url=url,
927
+ card=card
928
+ )
929
+
930
+ self._registered_agents[card.name] = agent
931
+ self.logger.info(f"Registered A2A agent: {card.name} ({url})")
932
+ return agent
933
+
934
+ except Exception as e:
935
+ self.logger.error(f"Error registering A2A agent from {url}: {e}")
936
+ raise
937
+
938
+ def get_a2a_agents(self) -> List[RegisteredAgent]:
939
+ """Get all registered A2A agents."""
940
+ return list(self._registered_agents.values())
941
+
942
+ def get_by_skill(self, skill: str) -> List[RegisteredAgent]:
943
+ """
944
+ Get agents that have a specific skill (by ID or name substring).
945
+ """
946
+ results = []
947
+ skill_lower = skill.lower()
948
+ for agent in self._registered_agents.values():
949
+ for s in agent.card.skills:
950
+ if skill_lower in s.id.lower() or skill_lower in s.name.lower():
951
+ results.append(agent)
952
+ break
953
+ return results
954
+
955
+ def get_by_tag(self, tag: str) -> List[RegisteredAgent]:
956
+ """Get agents that have a specific tag."""
957
+ results = []
958
+ tag_lower = tag.lower()
959
+ for agent in self._registered_agents.values():
960
+ # Check agent tags
961
+ if any(tag_lower == t.lower() for t in agent.card.tags):
962
+ results.append(agent)
963
+ continue
964
+ # Check skill tags
965
+ for s in agent.card.skills:
966
+ if any(tag_lower == t.lower() for t in s.tags):
967
+ results.append(agent)
968
+ break
969
+ return results
970
+
971
+ def search_a2a_agents(self, query: str) -> List[RegisteredAgent]:
972
+ """
973
+ Search agents by name, description, tags, or skills.
974
+ """
975
+ results = []
976
+ q = query.lower()
977
+ for agent in self._registered_agents.values():
978
+ # Search in agent metadata and tags
979
+ if (q in agent.card.name.lower() or
980
+ q in agent.card.description.lower() or
981
+ any(q in t.lower() for t in agent.card.tags)):
982
+ results.append(agent)
983
+ continue
984
+
985
+ # Search in skills
986
+ found_in_skills = False
987
+ for s in agent.card.skills:
988
+ if (q in s.name.lower() or
989
+ q in s.description.lower() or
990
+ any(q in t.lower() for t in s.tags)):
991
+ found_in_skills = True
992
+ break
993
+
994
+ if found_in_skills:
995
+ results.append(agent)
996
+
997
+ return results
998
+
999
+ def list_a2a_agents(self) -> List[str]:
1000
+ """List names of registered A2A agents."""
1001
+ return list(self._registered_agents.keys())
1002
+
1003
+ async def execute_tool_call(
1004
+ self,
1005
+ content_block: Dict[str, Any]
1006
+ ) -> Dict[str, Any]:
1007
+ """Execute a single tool call and return the result."""
1008
+ tool_name = content_block["name"]
1009
+ tool_input = content_block["input"]
1010
+ tool_id = content_block["id"]
1011
+
1012
+ try:
1013
+ tool_result = await self.execute_tool(tool_name, tool_input)
1014
+ return {
1015
+ "type": "tool_result",
1016
+ "tool_use_id": tool_id,
1017
+ "content": str(tool_result)
1018
+ }
1019
+ except Exception as e:
1020
+ return {
1021
+ "type": "tool_result",
1022
+ "tool_use_id": tool_id,
1023
+ "is_error": True,
1024
+ "content": str(e)
1025
+ }
1026
+
1027
+ def _postprocess_result(self, tool_name: str, out: Any, meta: Dict[str, Any]) -> None:
1028
+ """Auto-share DataFrame outputs and push to PythonPandasTool."""
1029
+ try:
1030
+ if not self.auto_share_dataframes:
1031
+ return
1032
+ # Handle single DF
1033
+ if isinstance(out, pd.DataFrame):
1034
+ # Prefer a semantic name when metadata exists
1035
+ base = meta.get("query_slug") or meta.get("name") or tool_name
1036
+ df_name = self._unique_df_name(base)
1037
+ self.share_dataframe(df_name, out, meta)
1038
+ return
1039
+ # Handle tuple or dict containers that embed a DataFrame under 'result'
1040
+ if isinstance(out, dict) and "result" in out and isinstance(out["result"], pd.DataFrame):
1041
+ base = meta.get("query_slug") or meta.get("name") or tool_name
1042
+ df_name = self._unique_df_name(base)
1043
+ self.share_dataframe(df_name, out["result"], meta)
1044
+ except Exception as e:
1045
+ self.logger.debug(f"No DF shared for {tool_name}: {e}")
1046
+
1047
+ def _unique_df_name(self, base: str) -> str:
1048
+ base = (base or "df").replace(" ", "_").lower()
1049
+ name = base
1050
+ i = 1
1051
+ while name in self._shared["dataframes"]:
1052
+ i += 1
1053
+ name = f"{base}_{i}"
1054
+ return name
1055
+
1056
+
1057
+ def tool_count(self) -> int:
1058
+ """Get the number of registered tools."""
1059
+ return len(self._tools)
1060
+
1061
+ def share_dataframe(self, name: str, df: "pd.DataFrame", meta: Dict[str, Any] = None) -> str:
1062
+ """Store df in shared context and push into python_pandas if present."""
1063
+ if not isinstance(df, pd.DataFrame):
1064
+ raise ValueError("share_dataframe expects a pandas.DataFrame")
1065
+ safe = name or f"df_{len(self._shared['dataframes'])+1}"
1066
+ self._shared["dataframes"][safe] = (df, meta or {})
1067
+ # auto-push into python_pandas
1068
+ if self.auto_push_to_pandas:
1069
+ pandas_tool = self.get_tool(self.pandas_tool_name)
1070
+ if pandas_tool:
1071
+ try:
1072
+ msg = pandas_tool.add_dataframe(safe, df, regenerate_guide=True)
1073
+ self.logger.debug(f"PandasTool: {msg}")
1074
+ except Exception as e:
1075
+ self.logger.warning(f"Could not push DF into {self.pandas_tool_name}: {e}")
1076
+ return safe
1077
+
1078
+ def get_shared_dataframe(self, name: str) -> "pd.DataFrame":
1079
+ df, _ = self._shared["dataframes"][name]
1080
+ return df
1081
+
1082
+ def list_shared_dataframes(self) -> List[str]:
1083
+ return list(self._shared["dataframes"].keys())
1084
+
1085
+ def clear_shared(self) -> None:
1086
+ self._shared = {"dataframes": {}}
1087
+
1088
+ # == Hooks ==
1089
+ def add_result_hook(self, fn: Callable[[str, Any, Dict[str, Any]], None]) -> None:
1090
+ """Register a function(tool_name, result, metadata) -> None run after each tool."""
1091
+ self._result_hooks.append(fn)
1092
+
1093
+ def _run_result_hooks(self, tool_name: str, result: Any, metadata: Dict[str, Any]) -> None:
1094
+ for fn in self._result_hooks:
1095
+ try:
1096
+ fn(tool_name, result, metadata)
1097
+ except Exception as e:
1098
+ self.logger.warning(f"Result hook error in {fn}: {e}")