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,904 @@
1
+ # parrot/tools/openapi_toolkit.py
2
+ """
3
+ OpenAPIToolkit - Dynamic toolkit that exposes OpenAPI services as tools.
4
+
5
+ IMPROVEMENTS IN THIS VERSION:
6
+ - Uses prance for robust OpenAPI parsing and reference resolution
7
+ - Inline schema refs via prance (no manual recursion)
8
+ - Support for application/x-www-form-urlencoded
9
+ - Optimized schemas for single-operation specs (cleaner LLM experience)
10
+
11
+ This toolkit automatically converts OpenAPI specifications into callable tools,
12
+ allowing LLMs to interact with REST APIs without manual tool definition.
13
+
14
+ Example:
15
+ toolkit = OpenAPIToolkit(
16
+ spec="https://petstore3.swagger.io/api/v3/openapi.json",
17
+ service="petstore"
18
+ )
19
+ tools = toolkit.get_tools()
20
+ # Creates tools like: petstore_get_pet, petstore_post_pet, etc.
21
+ """
22
+ from typing import Dict, List, Any, Optional, Union
23
+ import contextlib
24
+ import re
25
+ import json
26
+ from pathlib import Path
27
+ from urllib.parse import urlparse
28
+ import yaml
29
+ import httpx
30
+ from pydantic import BaseModel, Field, create_model
31
+ from navconfig.logging import logging
32
+
33
+ # Use prance for OpenAPI parsing with reference resolution
34
+ try:
35
+ from prance import ResolvingParser
36
+ PRANCE_AVAILABLE = True
37
+ except ImportError:
38
+ PRANCE_AVAILABLE = False
39
+
40
+ from ..interfaces.http import HTTPService
41
+ from .toolkit import AbstractToolkit
42
+ from .abstract import ToolResult
43
+
44
+
45
+ class OpenAPIToolkit(AbstractToolkit):
46
+ """
47
+ Toolkit that dynamically generates tools from OpenAPI specifications.
48
+
49
+ This toolkit:
50
+ - Uses prance for robust OpenAPI 3.x parsing (JSON/YAML, local or remote)
51
+ - Automatically resolves ALL $ref references (internal and external)
52
+ - Creates one tool per operation with naming: {service}_{method}_{path}
53
+ - Handles path parameters, query parameters, and request bodies
54
+ - Supports multiple content types: application/json, application/x-www-form-urlencoded
55
+ - Optimizes schemas for single-operation specs (cleaner for LLMs)
56
+ - Supports multiple authentication methods (API keys, Bearer tokens, Basic auth)
57
+
58
+ The tools are generated dynamically and integrated with HTTPService
59
+ for robust HTTP handling with retry logic, proxy support, etc.
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ spec: Union[str, Dict[str, Any]],
65
+ service: str,
66
+ base_url: Optional[str] = None,
67
+ api_key: Optional[str] = None,
68
+ auth_type: str = "bearer", # "bearer", "apikey", "basic"
69
+ auth_header: str = "Authorization",
70
+ api_key_location: str = "header", # "header", "query"
71
+ api_key_name: str = "api_key",
72
+ credentials: Optional[Dict[str, str]] = None,
73
+ use_proxy: bool = False,
74
+ timeout: int = 30,
75
+ debug: bool = False,
76
+ **kwargs
77
+ ):
78
+ """
79
+ Initialize OpenAPI toolkit.
80
+
81
+ Args:
82
+ spec: OpenAPI spec as JSON string, YAML string, URL, dict, or file path
83
+ service: Service name used as prefix for tool names (e.g., "petstore")
84
+ base_url: Override base URL from spec
85
+ api_key: API key for authentication
86
+ auth_type: Authentication type ("bearer", "apikey", "basic")
87
+ auth_header: Header name for authentication (default: "Authorization")
88
+ api_key_location: Where to put API key ("header" or "query")
89
+ api_key_name: Name of API key parameter (for query params)
90
+ credentials: Alternative credentials dict (username/password for basic auth)
91
+ use_proxy: Enable proxy usage
92
+ timeout: Request timeout in seconds
93
+ debug: Enable debug logging
94
+ **kwargs: Additional toolkit configuration
95
+ """
96
+ super().__init__(**kwargs)
97
+
98
+ self.service = service
99
+ self.debug = debug
100
+ self.logger = logging.getLogger(f'Parrot.Tools.OpenAPIToolkit.{service}')
101
+
102
+ # Load and parse OpenAPI spec with prance (auto-resolves all $refs)
103
+ self.raw_spec = self._load_spec_with_prance(spec)
104
+ self.spec = self.raw_spec # Already resolved by prance
105
+
106
+ # Extract base URL
107
+ self.base_url = base_url or self._extract_base_url()
108
+ if not self.base_url:
109
+ raise ValueError("No base URL found in spec and none provided")
110
+
111
+ # Validate base URL is absolute
112
+ if self.base_url.startswith('/'):
113
+ raise ValueError(
114
+ f"Base URL '{self.base_url}' is relative. "
115
+ "Please provide an absolute base_url parameter or ensure the OpenAPI spec "
116
+ "contains an absolute server URL. "
117
+ f"Example: OpenAPIToolkit(spec=..., service='{service}', "
118
+ f"base_url='https://example.com{self.base_url}')"
119
+ )
120
+
121
+ # Ensure base URL has protocol
122
+ parsed_base = urlparse(self.base_url)
123
+ if not parsed_base.scheme:
124
+ raise ValueError(
125
+ f"Base URL '{self.base_url}' is missing protocol (http:// or https://). "
126
+ "Please provide a complete base_url parameter."
127
+ )
128
+
129
+ # Setup authentication
130
+ self.api_key = api_key
131
+ self.auth_type = auth_type
132
+ self.auth_header = auth_header
133
+ self.api_key_location = api_key_location
134
+ self.api_key_name = api_key_name
135
+
136
+ # Prepare credentials and headers for HTTPService
137
+ creds = credentials or {}
138
+ headers = {}
139
+
140
+ if api_key:
141
+ if auth_type == "bearer":
142
+ creds['token'] = api_key
143
+ elif auth_type == "apikey":
144
+ if api_key_location == "header":
145
+ headers[auth_header] = api_key
146
+ # For query params, we'll add it per request
147
+ else:
148
+ creds['apikey'] = api_key
149
+
150
+ # Initialize HTTPService
151
+ self.http_service = HTTPService(
152
+ accept='application/json',
153
+ headers=headers,
154
+ credentials=creds,
155
+ use_proxy=use_proxy,
156
+ timeout=timeout,
157
+ debug=debug,
158
+ **kwargs
159
+ )
160
+
161
+ # Parse operations from spec
162
+ self.operations = self._parse_operations()
163
+
164
+ # OPTIMIZATION 3: Detect if this is a single-operation spec
165
+ self.is_single_operation = len(self.operations) == 1
166
+
167
+ if self.debug:
168
+ self.logger.debug(
169
+ f"Loaded {len(self.operations)} operations. "
170
+ f"Single operation mode: {self.is_single_operation}"
171
+ )
172
+
173
+ # Generate tools dynamically
174
+ self._generate_dynamic_methods()
175
+
176
+ def _load_spec_with_prance(self, spec: Union[str, Dict[str, Any]]) -> Dict[str, Any]:
177
+ """
178
+ Load OpenAPI specification using prance for automatic reference resolution.
179
+
180
+ prance automatically:
181
+ - Resolves ALL $ref references (internal and external)
182
+ - Validates OpenAPI spec
183
+ - Handles YAML and JSON
184
+ - Supports local files and URLs
185
+
186
+ Args:
187
+ spec: URL, file path, JSON/YAML string, or dict
188
+
189
+ Returns:
190
+ Fully resolved specification as dictionary (NEVER returns None)
191
+ """
192
+ # Track source URL for relative URL resolution
193
+ self._spec_source_url = None
194
+
195
+ # If already a dict, use prance to resolve it
196
+ if isinstance(spec, dict):
197
+ if PRANCE_AVAILABLE:
198
+ try:
199
+ # Create temporary file for prance to parse
200
+ import tempfile
201
+ temp_path = None
202
+ try:
203
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
204
+ json.dump(spec, f)
205
+ temp_path = f.name
206
+
207
+ parser = ResolvingParser(temp_path, lazy=True, strict=False)
208
+ resolved_spec = parser.specification
209
+
210
+ # Clean up temp file
211
+ if temp_path:
212
+ Path(temp_path).unlink(missing_ok=True)
213
+
214
+ # Ensure we have a valid spec
215
+ if resolved_spec and isinstance(resolved_spec, dict):
216
+ return resolved_spec
217
+ else:
218
+ self.logger.warning("prance returned invalid spec, using original dict")
219
+ return spec
220
+
221
+ except Exception as inner_e:
222
+ # Clean up temp file on error
223
+ if temp_path:
224
+ with contextlib.suppress(Exception):
225
+ Path(temp_path).unlink(missing_ok=True)
226
+ raise inner_e
227
+
228
+ except Exception as e:
229
+ self.logger.warning(f"prance parsing failed, using dict as-is: {e}")
230
+ # ALWAYS return the original spec as fallback
231
+ return spec
232
+ else:
233
+ # Prance not available, return original dict
234
+ return spec
235
+
236
+ # Check if it's a URL
237
+ parsed = urlparse(spec)
238
+ if parsed.scheme in ('http', 'https'):
239
+ if self.debug:
240
+ self.logger.debug(f"Loading spec from URL: {spec}")
241
+ self._spec_source_url = spec
242
+
243
+ if PRANCE_AVAILABLE:
244
+ # Let prance handle the URL directly
245
+ try:
246
+ parser = ResolvingParser(spec, lazy=True, strict=False)
247
+ resolved_spec = parser.specification
248
+ if resolved_spec and isinstance(resolved_spec, dict):
249
+ return resolved_spec
250
+ except Exception as e:
251
+ self.logger.warning(f"prance URL parsing failed: {e}")
252
+ # Fall through to manual loading
253
+
254
+ # Fallback: manual download and parse
255
+ response = httpx.get(spec, timeout=30)
256
+ response.raise_for_status()
257
+ content = response.text
258
+
259
+ # Parse manually
260
+ try:
261
+ loaded = yaml.safe_load(content)
262
+ if loaded and isinstance(loaded, dict):
263
+ return loaded
264
+ except Exception:
265
+ pass
266
+
267
+ # Try JSON
268
+ loaded = json.loads(content)
269
+ if loaded and isinstance(loaded, dict):
270
+ return loaded
271
+
272
+ raise ValueError(f"Failed to parse spec from URL: {spec}")
273
+
274
+ # Check if it's a file path
275
+ elif Path(spec).exists():
276
+ if self.debug:
277
+ self.logger.debug(f"Loading spec from file: {spec}")
278
+
279
+ if PRANCE_AVAILABLE:
280
+ try:
281
+ parser = ResolvingParser(str(spec), lazy=True, strict=False)
282
+ resolved_spec = parser.specification
283
+ if resolved_spec and isinstance(resolved_spec, dict):
284
+ return resolved_spec
285
+ except Exception as e:
286
+ self.logger.warning(f"prance file parsing failed: {e}")
287
+ # Fall through to manual loading
288
+
289
+ # Fallback: manual loading
290
+ with open(spec, 'r', encoding='utf-8') as f:
291
+ content = f.read()
292
+
293
+ try:
294
+ loaded = yaml.safe_load(content)
295
+ if loaded and isinstance(loaded, dict):
296
+ return loaded
297
+ except Exception:
298
+ pass
299
+
300
+ # Try JSON
301
+ loaded = json.loads(content)
302
+ if loaded and isinstance(loaded, dict):
303
+ return loaded
304
+
305
+ raise ValueError(f"Failed to parse spec from file: {spec}")
306
+
307
+ # Otherwise, treat as string content
308
+ else:
309
+ if self.debug:
310
+ self.logger.debug("Parsing spec from string")
311
+
312
+ # Try YAML first (superset of JSON)
313
+ try:
314
+ loaded = yaml.safe_load(spec)
315
+ if loaded and isinstance(loaded, dict):
316
+ return loaded
317
+ except Exception:
318
+ pass
319
+
320
+ # Try JSON
321
+ try:
322
+ loaded = json.loads(spec)
323
+ if loaded and isinstance(loaded, dict):
324
+ return loaded
325
+ except Exception:
326
+ pass
327
+
328
+ raise ValueError(
329
+ "Failed to parse spec from string"
330
+ )
331
+
332
+ def _extract_base_url(self) -> Optional[str]:
333
+ """Extract base URL from OpenAPI servers section."""
334
+ servers = self.spec.get('servers', [])
335
+ if servers and len(servers) > 0:
336
+ server_url = servers[0].get('url', '')
337
+
338
+ # Handle server variables if present
339
+ variables = servers[0].get('variables', {})
340
+ for var_name, var_config in variables.items():
341
+ default_value = var_config.get('default', '')
342
+ server_url = server_url.replace(f'{{{var_name}}}', default_value)
343
+
344
+ # If server URL is relative (starts with /), construct absolute URL
345
+ # from the spec source if it was loaded from URL
346
+ if server_url.startswith('/') and hasattr(self, '_spec_source_url') and self._spec_source_url:
347
+ parsed = urlparse(self._spec_source_url)
348
+ base = f"{parsed.scheme}://{parsed.netloc}"
349
+ server_url = base + server_url
350
+
351
+ return server_url
352
+ return None
353
+
354
+ def _parse_operations(self) -> List[Dict[str, Any]]:
355
+ """
356
+ Parse OpenAPI paths into operation definitions.
357
+
358
+ Each operation includes:
359
+ - operation_id: Unique identifier
360
+ - path: API path
361
+ - method: HTTP method
362
+ - parameters: Path, query, and header params
363
+ - request_body: Body schema with content type info
364
+ - description/summary: Operation description
365
+ """
366
+ operations = []
367
+
368
+ for path, path_item in self.spec.get('paths', {}).items():
369
+ # HTTP methods
370
+ for method in ['get', 'post', 'put', 'patch', 'delete', 'head', 'options']:
371
+ if method not in path_item:
372
+ continue
373
+
374
+ operation_spec = path_item[method]
375
+
376
+ # Generate operation ID if not present
377
+ operation_id = operation_spec.get(
378
+ 'operationId',
379
+ f"{method}_{path.replace('/', '_').replace('{', '').replace('}', '')}"
380
+ )
381
+
382
+ # Parse parameters
383
+ parameters = {
384
+ 'path': [],
385
+ 'query': [],
386
+ 'header': [],
387
+ 'cookie': []
388
+ }
389
+
390
+ for param in operation_spec.get('parameters', []):
391
+ param_in = param.get('in', 'query')
392
+ if param_in in parameters:
393
+ parameters[param_in].append(param)
394
+
395
+ # IMPROVEMENT 2: Parse request body with content type detection
396
+ request_body = None
397
+ if 'requestBody' in operation_spec:
398
+ request_body_spec = operation_spec['requestBody']
399
+ content = request_body_spec.get('content', {})
400
+
401
+ # Detect content type - prioritize JSON, then form-urlencoded
402
+ content_type = None
403
+ schema = None
404
+
405
+ if 'application/json' in content:
406
+ content_type = 'application/json'
407
+ schema = content['application/json'].get('schema', {})
408
+ elif 'application/x-www-form-urlencoded' in content:
409
+ content_type = 'application/x-www-form-urlencoded'
410
+ schema = content['application/x-www-form-urlencoded'].get('schema', {})
411
+ elif content:
412
+ # Fallback to first available content type
413
+ content_type = list(content.keys())[0]
414
+ schema = content[content_type].get('schema', {})
415
+
416
+ if schema:
417
+ request_body = {
418
+ 'schema': schema,
419
+ 'required': request_body_spec.get('required', False),
420
+ 'description': request_body_spec.get('description', ''),
421
+ 'content_type': content_type # Track content type
422
+ }
423
+
424
+ operations.append({
425
+ 'operation_id': operation_id,
426
+ 'path': path,
427
+ 'method': method.upper(),
428
+ 'parameters': parameters,
429
+ 'request_body': request_body,
430
+ 'summary': operation_spec.get('summary', ''),
431
+ 'description': operation_spec.get('description', ''),
432
+ })
433
+
434
+ return operations
435
+
436
+ def _normalize_path_for_method_name(self, path: str) -> str:
437
+ """
438
+ Normalize path for method name.
439
+
440
+ Examples:
441
+ /pet/{petId} -> pet_petid
442
+ /store/inventory -> store_inventory
443
+ /user/login -> user_login
444
+ """
445
+ # Remove leading/trailing slashes
446
+ path = path.strip('/')
447
+
448
+ # Replace path parameters {petId} with just petid
449
+ path = re.sub(r'\{([^}]+)\}', r'\1', path)
450
+
451
+ # Replace slashes and special chars with underscores
452
+ path = re.sub(r'[^a-zA-Z0-9]+', '_', path)
453
+
454
+ # Convert to lowercase
455
+ path = path.lower()
456
+
457
+ return path
458
+
459
+ def _resolve_schema_ref(self, schema: Dict[str, Any]) -> Dict[str, Any]:
460
+ """
461
+ Manually resolve $ref if present.
462
+
463
+ This is a fallback for when prance fails to resolve references.
464
+ Works with internal references only (#/components/schemas/...).
465
+ """
466
+ if '$ref' not in schema:
467
+ return schema
468
+
469
+ ref_path = schema['$ref']
470
+ if not ref_path.startswith('#/'):
471
+ # External refs not supported in this fallback
472
+ return schema
473
+
474
+ # Navigate the reference path
475
+ parts = ref_path[2:].split('/') # Remove '#/' prefix
476
+ resolved = self.spec
477
+
478
+ try:
479
+ for part in parts:
480
+ resolved = resolved[part]
481
+
482
+ # Recursively resolve nested refs
483
+ return self._resolve_schema_ref(resolved)
484
+ except (KeyError, TypeError):
485
+ # If resolution fails, return original
486
+ return schema
487
+
488
+ def _create_pydantic_schema(
489
+ self,
490
+ operation: Dict[str, Any]
491
+ ) -> type[BaseModel]:
492
+ """
493
+ Create Pydantic model for operation parameters.
494
+
495
+ IMPROVEMENT 3: For single-operation specs, create minimal schema
496
+ without redundant path/method fields for better LLM experience.
497
+ """
498
+ fields = {}
499
+
500
+ # OPTIMIZATION: Skip path/method fields for single-operation specs
501
+ skip_meta_fields = self.is_single_operation
502
+
503
+ # Add path parameters (always required)
504
+ for param in operation['parameters'].get('path', []):
505
+ field_type = self._openapi_type_to_python(param['schema'])
506
+ field_info = Field(
507
+ description=param.get('description', f"Path parameter: {param['name']}")
508
+ )
509
+ fields[param['name']] = (field_type, field_info)
510
+
511
+ # Add query parameters
512
+ for param in operation['parameters'].get('query', []):
513
+ field_type = self._openapi_type_to_python(param['schema'])
514
+ if is_required := param.get('required', False):
515
+ field_info = Field(
516
+ description=param.get('description', f"Query parameter: {param['name']}")
517
+ )
518
+ fields[param['name']] = (field_type, field_info)
519
+ else:
520
+ field_info = Field(
521
+ default=None,
522
+ description=param.get('description', f"Query parameter: {param['name']}")
523
+ )
524
+ fields[param['name']] = (Optional[field_type], field_info)
525
+
526
+ # Add header parameters (usually optional)
527
+ for param in operation['parameters'].get('header', []):
528
+ field_type = self._openapi_type_to_python(param['schema'])
529
+ if is_required := param.get('required', False):
530
+ field_info = Field(
531
+ description=param.get('description', f"Header parameter: {param['name']}")
532
+ )
533
+ fields[param['name']] = (field_type, field_info)
534
+ else:
535
+ field_info = Field(
536
+ default=None,
537
+ description=param.get('description', f"Header parameter: {param['name']}")
538
+ )
539
+ fields[param['name']] = (Optional[field_type], field_info)
540
+
541
+ # Add request body fields
542
+ if operation['request_body']:
543
+ schema = operation['request_body']['schema']
544
+
545
+ # CRITICAL FIX: Manually resolve $ref if prance failed
546
+ # This ensures we always get the actual schema with type and properties
547
+ schema = self._resolve_schema_ref(schema)
548
+
549
+ # If request body is a single object, flatten its properties
550
+ if schema.get('type') == 'object' and 'properties' in schema:
551
+ for field_name, field_schema in schema['properties'].items():
552
+ field_type = self._openapi_type_to_python(field_schema)
553
+ required = field_name in schema.get('required', [])
554
+
555
+ if required:
556
+ field_info = Field(
557
+ description=field_schema.get('description', f"Body parameter: {field_name}")
558
+ )
559
+ fields[field_name] = (field_type, field_info)
560
+ else:
561
+ field_info = Field(
562
+ default=None,
563
+ description=field_schema.get('description', f"Body parameter: {field_name}")
564
+ )
565
+ fields[field_name] = (Optional[field_type], field_info)
566
+ else:
567
+ # For non-object bodies, create a single 'body' field
568
+ field_type = self._openapi_type_to_python(schema)
569
+ if is_required := operation['request_body'].get('required', False):
570
+ field_info = Field(
571
+ description=operation['request_body'].get('description', 'Request body')
572
+ )
573
+ fields['body'] = (field_type, field_info)
574
+ else:
575
+ field_info = Field(
576
+ default=None,
577
+ description=operation['request_body'].get('description', 'Request body')
578
+ )
579
+ fields['body'] = (Optional[field_type], field_info)
580
+
581
+ # Create dynamic model
582
+ model_name = f"{operation['operation_id']}_Schema"
583
+
584
+ # If no fields, create empty schema
585
+ if not fields:
586
+ return create_model(model_name)
587
+
588
+ return create_model(model_name, **fields)
589
+
590
+ def _openapi_type_to_python(self, schema: Dict[str, Any]) -> type:
591
+ """
592
+ Convert OpenAPI schema type to Python type.
593
+
594
+ Args:
595
+ schema: OpenAPI schema definition
596
+
597
+ Returns:
598
+ Corresponding Python type
599
+ """
600
+ schema_type = schema.get('type', 'string')
601
+ schema_format = schema.get('format')
602
+
603
+ # Handle arrays
604
+ if schema_type == 'array':
605
+ items_schema = schema.get('items', {'type': 'string'})
606
+ item_type = self._openapi_type_to_python(items_schema)
607
+ return List[item_type]
608
+
609
+ # Handle objects (as dict)
610
+ if schema_type == 'object':
611
+ return Dict[str, Any]
612
+
613
+ # Handle primitive types
614
+ type_mapping = {
615
+ 'string': str,
616
+ 'integer': int,
617
+ 'number': float,
618
+ 'boolean': bool,
619
+ }
620
+
621
+ # Consider format for more specific types
622
+ if schema_type == 'integer' and schema_format == 'int64':
623
+ return int
624
+ if schema_type == 'number' and schema_format == 'float':
625
+ return float
626
+
627
+ return type_mapping.get(schema_type, str)
628
+
629
+ def _generate_dynamic_methods(self):
630
+ """
631
+ Generate dynamic async methods for each operation.
632
+
633
+ This is the magic that converts OpenAPI operations into toolkit methods.
634
+ Each method becomes a tool automatically via AbstractToolkit.get_tools().
635
+ """
636
+ for operation in self.operations:
637
+ # Generate method name: {service}_{method}_{normalized_path}
638
+ method_name = self._create_method_name(operation)
639
+
640
+ # Create the async method
641
+ async_method = self._create_operation_method(operation)
642
+
643
+ # Create and attach Pydantic schema for argument validation
644
+ pydantic_schema = self._create_pydantic_schema(operation)
645
+ async_method._args_schema = pydantic_schema
646
+
647
+ # Bind method to instance
648
+ bound_method = async_method.__get__(self, self.__class__)
649
+ setattr(self, method_name, bound_method)
650
+
651
+ if self.debug:
652
+ self.logger.debug(
653
+ f"Created tool method: {method_name} "
654
+ f"for {operation['method']} {operation['path']}"
655
+ )
656
+
657
+ def _create_method_name(self, operation: Dict[str, Any]) -> str:
658
+ """
659
+ Create method name following convention: {service}_{method}_{path}
660
+
661
+ Examples:
662
+ petstore_get_pet_petid
663
+ petstore_post_pet
664
+ petstore_get_store_inventory
665
+ """
666
+ method = operation['method'].lower()
667
+ path = self._normalize_path_for_method_name(operation['path'])
668
+
669
+ # Combine with service prefix
670
+ method_name = f"{self.service}_{method}_{path}"
671
+
672
+ # Clean up multiple underscores
673
+ method_name = re.sub(r'_+', '_', method_name)
674
+
675
+ # Remove trailing underscores
676
+ method_name = method_name.strip('_')
677
+
678
+ return method_name
679
+
680
+ def _create_operation_method(self, operation: Dict[str, Any]):
681
+ """
682
+ Create an async method that executes the OpenAPI operation.
683
+
684
+ This method will be called when the LLM uses the tool.
685
+ """
686
+ # Create the implementation
687
+ async def operation_method(self_ref, **kwargs) -> Dict[str, Any]:
688
+ """
689
+ Execute OpenAPI operation.
690
+
691
+ This docstring will be dynamically set for each operation.
692
+ """
693
+ try:
694
+ # Build URL with path parameters
695
+ url = self_ref._build_operation_url(operation, kwargs)
696
+
697
+ # Separate query parameters
698
+ query_params = self_ref._extract_query_params(operation, kwargs)
699
+
700
+ # Extract header parameters
701
+ header_params = self_ref._extract_header_params(operation, kwargs)
702
+
703
+ # IMPROVEMENT 2: Build request body with content type handling
704
+ body_data, content_type = self_ref._extract_body_data(operation, kwargs)
705
+
706
+ # Make request
707
+ method = operation['method']
708
+
709
+ if self_ref.debug:
710
+ self_ref.logger.debug(
711
+ f"Executing {method} {url} with "
712
+ f"params={query_params}, headers={header_params}, "
713
+ f"body={body_data}, content_type={content_type}"
714
+ )
715
+
716
+ # Prepare request headers
717
+ request_headers = header_params.copy() if header_params else {}
718
+
719
+ # Determine content type and prepare request body
720
+ use_json = True
721
+ if method in ['POST', 'PUT', 'PATCH'] and body_data:
722
+ if content_type == 'application/x-www-form-urlencoded':
723
+ # Send as form data
724
+ use_json = False
725
+ request_headers['Content-Type'] = 'application/x-www-form-urlencoded'
726
+
727
+ # Create request kwargs
728
+ request_kwargs = {
729
+ 'url': url,
730
+ 'method': method,
731
+ 'params': query_params,
732
+ }
733
+
734
+ # Only add headers if we have any
735
+ if request_headers:
736
+ request_kwargs['headers'] = request_headers
737
+
738
+ # Add body data for POST/PUT/PATCH
739
+ if method in ['POST', 'PUT', 'PATCH'] and body_data:
740
+ request_kwargs['use_json'] = use_json
741
+ request_kwargs['data'] = body_data
742
+
743
+ # Execute request via HTTPService
744
+ result, error = await self_ref.http_service.request(
745
+ **request_kwargs,
746
+ full_response=False,
747
+ )
748
+
749
+ if error:
750
+ return ToolResult(
751
+ status="error",
752
+ result=None,
753
+ error=str(error),
754
+ metadata={
755
+ 'operation_id': operation['operation_id'],
756
+ 'method': method,
757
+ 'url': url,
758
+ }
759
+ ).model_dump()
760
+
761
+ return ToolResult(
762
+ status="success",
763
+ result=result,
764
+ metadata={
765
+ 'operation_id': operation['operation_id'],
766
+ 'method': method,
767
+ 'url': url,
768
+ }
769
+ ).model_dump()
770
+
771
+ except Exception as e:
772
+ self_ref.logger.error(f"Error executing operation: {e}", exc_info=True)
773
+ return ToolResult(
774
+ status="error",
775
+ result=None,
776
+ error=str(e),
777
+ metadata={'operation_id': operation['operation_id']}
778
+ ).model_dump()
779
+
780
+ # Set dynamic docstring
781
+ description = (
782
+ operation.get('description') or
783
+ operation.get('summary', '') or
784
+ f"{operation['method']} {operation['path']}"
785
+ )
786
+
787
+ operation_method.__doc__ = f"{description}\n\nOperation: {operation['operation_id']}"
788
+ operation_method.__name__ = self._create_method_name(operation)
789
+
790
+ # Store operation for later reference
791
+ operation_method._operation = operation
792
+
793
+ return operation_method
794
+
795
+ def _build_operation_url(
796
+ self,
797
+ operation: Dict[str, Any],
798
+ params: Dict[str, Any]
799
+ ) -> str:
800
+ """
801
+ Build complete URL with path parameters substituted.
802
+
803
+ Args:
804
+ operation: Operation definition
805
+ params: Parameters provided by LLM
806
+
807
+ Returns:
808
+ Complete URL with path params substituted
809
+ """
810
+ path = operation['path']
811
+
812
+ # Substitute path parameters
813
+ for param in operation['parameters'].get('path', []):
814
+ param_name = param['name']
815
+ if param_name in params:
816
+ placeholder = f"{{{param_name}}}"
817
+ path = path.replace(placeholder, str(params[param_name]))
818
+
819
+ # Combine with base URL
820
+ return f"{self.base_url.rstrip('/')}/{path.lstrip('/')}"
821
+
822
+ def _extract_query_params(
823
+ self,
824
+ operation: Dict[str, Any],
825
+ params: Dict[str, Any]
826
+ ) -> Dict[str, Any]:
827
+ """Extract query parameters from provided params."""
828
+ query_params = {}
829
+
830
+ for param in operation['parameters'].get('query', []):
831
+ param_name = param['name']
832
+ if param_name in params and params[param_name] is not None:
833
+ query_params[param_name] = params[param_name]
834
+
835
+ # Add API key if configured for query params
836
+ if self.api_key and self.api_key_location == "query":
837
+ query_params[self.api_key_name] = self.api_key
838
+
839
+ return query_params
840
+
841
+ def _extract_header_params(
842
+ self,
843
+ operation: Dict[str, Any],
844
+ params: Dict[str, Any]
845
+ ) -> Dict[str, str]:
846
+ """Extract header parameters from provided params."""
847
+ header_params = {}
848
+
849
+ for param in operation['parameters'].get('header', []):
850
+ param_name = param['name']
851
+ if param_name in params and params[param_name] is not None:
852
+ header_params[param_name] = str(params[param_name])
853
+
854
+ return header_params
855
+
856
+ def _extract_body_data(
857
+ self,
858
+ operation: Dict[str, Any],
859
+ params: Dict[str, Any]
860
+ ) -> tuple[Optional[Dict[str, Any]], Optional[str]]:
861
+ """
862
+ Extract request body data from provided params.
863
+
864
+ IMPROVEMENT 2: Returns tuple of (body_data, content_type) to handle
865
+ different content types like application/json and application/x-www-form-urlencoded
866
+
867
+ Returns:
868
+ Tuple of (body_data, content_type)
869
+ """
870
+ if not operation['request_body']:
871
+ return None, None
872
+
873
+ # Get schema and content type
874
+ schema = operation['request_body']['schema']
875
+ content_type = operation['request_body'].get('content_type', 'application/json')
876
+
877
+ # If schema is an object with properties, extract those fields
878
+ if schema.get('type') == 'object' and 'properties' in schema:
879
+ body = {
880
+ prop_name: params[prop_name]
881
+ for prop_name in schema['properties'].keys()
882
+ if prop_name in params and params[prop_name] is not None
883
+ }
884
+ return body or None, content_type
885
+
886
+ # Otherwise, look for a 'body' parameter
887
+ if 'body' in params:
888
+ return params['body'], content_type
889
+
890
+ # Fallback: use all non-path, non-query, and non-header params
891
+ path_params = {p['name'] for p in operation['parameters'].get('path', [])}
892
+ query_params = {p['name'] for p in operation['parameters'].get('query', [])}
893
+ header_params = {p['name'] for p in operation['parameters'].get('header', [])}
894
+
895
+ body = {
896
+ key: value
897
+ for key, value in params.items()
898
+ if key not in path_params
899
+ and key not in query_params
900
+ and key not in header_params
901
+ and value is not None
902
+ }
903
+
904
+ return body or None, content_type