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,508 @@
1
+ """Git/GitHub toolkit inspired by :mod:`parrot.tools.jiratoolkit`.
2
+
3
+ This toolkit focuses on two complementary workflows that frequently appear
4
+ in software review loops:
5
+
6
+ * producing a ``git apply`` compatible patch from pieces of code supplied by
7
+ an agent or user; and
8
+ * turning those code snippets into an actionable GitHub pull request via the
9
+ public REST API.
10
+
11
+ The implementation deliberately mirrors the structure of
12
+ ``JiraToolkit``—async public methods automatically become tools thanks to the
13
+ ``AbstractToolkit`` base class—so that it can be dropped into existing agent
14
+ configurations with minimal friction.
15
+
16
+ Only standard library modules (plus :mod:`requests` and :mod:`pydantic`, which
17
+ are already dependencies of Parrot) are required.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import asyncio
23
+ import base64
24
+ import datetime as _dt
25
+ import os
26
+ from dataclasses import dataclass
27
+ from typing import Any, Dict, List, Literal, Optional
28
+
29
+ import difflib
30
+
31
+ import requests
32
+ from pydantic import BaseModel, Field, model_validator
33
+
34
+ from .decorators import tool_schema
35
+ from .toolkit import AbstractToolkit
36
+
37
+
38
+ class GitToolkitError(RuntimeError):
39
+ """Raised when the toolkit cannot satisfy a request."""
40
+
41
+
42
+ class GitToolkitInput(BaseModel):
43
+ """Default configuration shared by all tools in the toolkit."""
44
+
45
+ default_repository: Optional[str] = Field(
46
+ default=None,
47
+ description="Default GitHub repository in 'owner/name' format.",
48
+ )
49
+ default_branch: str = Field(
50
+ default="main", description="Fallback branch used for pull requests."
51
+ )
52
+ github_token: Optional[str] = Field(
53
+ default=None,
54
+ description="Personal access token with repo scope for GitHub calls.",
55
+ )
56
+
57
+
58
+ class GitPatchFile(BaseModel):
59
+ """Represents a single file change for patch generation."""
60
+
61
+ path: str = Field(description="Path to the file inside the repository.")
62
+ change_type: Literal["modify", "add", "delete"] = Field(
63
+ default="modify",
64
+ description="Type of change represented by this patch fragment.",
65
+ )
66
+ original: Optional[str] = Field(
67
+ default=None,
68
+ description="Original file contents relevant to the change.",
69
+ )
70
+ updated: Optional[str] = Field(
71
+ default=None,
72
+ description="Updated file contents to apply.",
73
+ )
74
+ from_path: Optional[str] = Field(
75
+ default=None,
76
+ description="Override the 'from' path in the generated diff.",
77
+ )
78
+ to_path: Optional[str] = Field(
79
+ default=None,
80
+ description="Override the 'to' path in the generated diff.",
81
+ )
82
+
83
+ @model_validator(mode="after")
84
+ def _validate_payload(self) -> "GitPatchFile": # pragma: no cover - pydantic hook
85
+ """Ensure the required content is supplied for the selected change."""
86
+
87
+ if self.change_type == "modify":
88
+ if self.original is None or self.updated is None:
89
+ raise ValueError("modify changes require both original and updated code")
90
+ elif self.change_type == "add":
91
+ if self.updated is None:
92
+ raise ValueError("add changes require the updated code")
93
+ elif self.change_type == "delete":
94
+ if self.original is None:
95
+ raise ValueError("delete changes require the original code")
96
+ return self
97
+
98
+
99
+ class GeneratePatchInput(BaseModel):
100
+ """Input payload for ``generate_git_apply_patch``."""
101
+
102
+ files: List[GitPatchFile] = Field(
103
+ description="Collection of file changes that should be turned into a unified diff.",
104
+ )
105
+ context_lines: int = Field(
106
+ default=3,
107
+ ge=0,
108
+ description="How many context lines to include in the diff output.",
109
+ )
110
+ include_apply_snippet: bool = Field(
111
+ default=True,
112
+ description="If true, include a ready-to-run git-apply heredoc snippet.",
113
+ )
114
+
115
+
116
+ class GitHubFileChange(BaseModel):
117
+ """Description of a file mutation when creating a pull request."""
118
+
119
+ path: str = Field(description="File path inside the repository.")
120
+ content: Optional[str] = Field(
121
+ default=None,
122
+ description="New file content. Leave ``None`` to delete a file.",
123
+ )
124
+ encoding: Literal["utf-8", "base64"] = Field(
125
+ default="utf-8", description="Encoding used for ``content``."
126
+ )
127
+ message: Optional[str] = Field(
128
+ default=None,
129
+ description="Optional commit message just for this file change.",
130
+ )
131
+ change_type: Literal["modify", "add", "delete"] = Field(
132
+ default="modify", description="Type of change performed on the file."
133
+ )
134
+
135
+ @model_validator(mode="after")
136
+ def _validate_content(self) -> "GitHubFileChange": # pragma: no cover - pydantic hook
137
+ """Ensure ``content`` is present unless this is a deletion."""
138
+
139
+ if self.change_type == "delete" and self.content is not None:
140
+ raise ValueError("delete operations should not provide new content")
141
+ if self.change_type in {"modify", "add"} and self.content is None:
142
+ raise ValueError("modify/add operations require content")
143
+ return self
144
+
145
+
146
+ class CreatePullRequestInput(BaseModel):
147
+ """Input payload for ``create_pull_request``."""
148
+
149
+ repository: Optional[str] = Field(
150
+ default=None, description="Target GitHub repository in 'owner/name' format."
151
+ )
152
+ title: str = Field(description="Pull request title")
153
+ body: Optional[str] = Field(default=None, description="Pull request description")
154
+ base_branch: Optional[str] = Field(
155
+ default=None, description="Branch into which the changes should merge."
156
+ )
157
+ head_branch: Optional[str] = Field(
158
+ default=None, description="Branch name to create and push changes onto."
159
+ )
160
+ commit_message: Optional[str] = Field(
161
+ default=None,
162
+ description="Commit message used for the updates (defaults to title).",
163
+ )
164
+ files: List[GitHubFileChange] = Field(
165
+ description="List of file updates that compose the pull request.",
166
+ )
167
+ draft: bool = Field(
168
+ default=False, description="Create the pull request as a draft if true."
169
+ )
170
+ labels: Optional[List[str]] = Field(
171
+ default=None, description="Optional labels to apply after PR creation."
172
+ )
173
+
174
+
175
+ @dataclass
176
+ class _GitHubContext:
177
+ """Simple container with prepared GitHub configuration."""
178
+
179
+ repository: str
180
+ base_branch: str
181
+ token: str
182
+
183
+
184
+ class GitToolkit(AbstractToolkit):
185
+ """Toolkit dedicated to Git patch generation and GitHub pull requests."""
186
+
187
+ input_class = GitToolkitInput
188
+
189
+ def __init__(
190
+ self,
191
+ default_repository: Optional[str] = None,
192
+ default_branch: str = "main",
193
+ github_token: Optional[str] = None,
194
+ **kwargs: Any,
195
+ ) -> None:
196
+ super().__init__(**kwargs)
197
+
198
+ self.default_repository = (
199
+ default_repository
200
+ or os.getenv("GIT_DEFAULT_REPOSITORY")
201
+ or os.getenv("GITHUB_REPOSITORY")
202
+ )
203
+ self.default_branch = (
204
+ default_branch or os.getenv("GIT_DEFAULT_BRANCH") or "main"
205
+ )
206
+ self.github_token = github_token or os.getenv("GITHUB_TOKEN")
207
+
208
+ # ------------------------------------------------------------------
209
+ # Patch generation helpers
210
+ # ------------------------------------------------------------------
211
+ @staticmethod
212
+ def _ensure_trailing_newline(text: str) -> str:
213
+ """Return ``text`` ensuring it terminates with a single newline."""
214
+
215
+ if text.endswith("\n"):
216
+ return text
217
+ return f"{text}\n"
218
+
219
+ @staticmethod
220
+ def _make_diff_fragment(
221
+ change: GitPatchFile,
222
+ context_lines: int,
223
+ ) -> Optional[str]:
224
+ """Produce a unified diff fragment for ``change``."""
225
+
226
+ from_path = change.from_path or f"a/{change.path}"
227
+ to_path = change.to_path or f"b/{change.path}"
228
+
229
+ if change.change_type == "add":
230
+ original_lines: List[str] = []
231
+ updated_lines = GitToolkit._ensure_trailing_newline(change.updated or "").splitlines(True)
232
+ from_path = "/dev/null"
233
+ elif change.change_type == "delete":
234
+ original_lines = GitToolkit._ensure_trailing_newline(change.original or "").splitlines(True)
235
+ updated_lines = []
236
+ to_path = "/dev/null"
237
+ else:
238
+ original_lines = GitToolkit._ensure_trailing_newline(change.original or "").splitlines(True)
239
+ updated_lines = GitToolkit._ensure_trailing_newline(change.updated or "").splitlines(True)
240
+
241
+ diff = list(
242
+ difflib.unified_diff(
243
+ original_lines,
244
+ updated_lines,
245
+ fromfile=from_path,
246
+ tofile=to_path,
247
+ n=context_lines,
248
+ )
249
+ )
250
+
251
+ if not diff:
252
+ return None
253
+
254
+ # Ensure diff chunks end with a newline to keep git-apply happy.
255
+ diff_text = "".join(diff)
256
+ if not diff_text.endswith("\n"):
257
+ diff_text += "\n"
258
+ return diff_text
259
+
260
+ def _render_patch(
261
+ self,
262
+ files: List[GitPatchFile],
263
+ context_lines: int,
264
+ include_apply_snippet: bool,
265
+ ) -> Dict[str, Any]:
266
+ fragments: List[str] = []
267
+ skipped: List[str] = []
268
+
269
+ for change in files:
270
+ fragment = self._make_diff_fragment(change, context_lines)
271
+ if fragment:
272
+ fragments.append(fragment)
273
+ else:
274
+ skipped.append(change.path)
275
+
276
+ if not fragments:
277
+ raise GitToolkitError("No differences detected across the provided files.")
278
+
279
+ patch = "".join(fragments)
280
+ apply_snippet = None
281
+ if include_apply_snippet:
282
+ apply_snippet = "cat <<'PATCH' | git apply -\n" + patch + "PATCH\n"
283
+
284
+ return {
285
+ "patch": patch,
286
+ "git_apply": apply_snippet,
287
+ "files": len(files),
288
+ "skipped": skipped,
289
+ }
290
+
291
+ @tool_schema(GeneratePatchInput)
292
+ async def generate_git_apply_patch(
293
+ self,
294
+ files: List[GitPatchFile],
295
+ context_lines: int = 3,
296
+ include_apply_snippet: bool = True,
297
+ ) -> Dict[str, Any]:
298
+ """Create a unified diff (and optional ``git apply`` snippet) from code blocks."""
299
+
300
+ return await asyncio.to_thread(
301
+ self._render_patch, files, context_lines, include_apply_snippet
302
+ )
303
+
304
+ # ------------------------------------------------------------------
305
+ # GitHub helpers
306
+ # ------------------------------------------------------------------
307
+ def _prepare_github_context(
308
+ self, repository: Optional[str], base_branch: Optional[str]
309
+ ) -> _GitHubContext:
310
+ repo = repository or self.default_repository
311
+ if not repo:
312
+ raise GitToolkitError(
313
+ "A target repository is required (pass repository or configure default)."
314
+ )
315
+
316
+ token = self.github_token
317
+ if not token:
318
+ raise GitToolkitError(
319
+ "A GitHub personal access token is required via init argument or GITHUB_TOKEN."
320
+ )
321
+
322
+ branch = base_branch or self.default_branch
323
+ return _GitHubContext(repository=repo, base_branch=branch, token=token)
324
+
325
+ @staticmethod
326
+ def _request(
327
+ method: str,
328
+ url: str,
329
+ token: str,
330
+ *,
331
+ expected: int,
332
+ **kwargs: Any,
333
+ ) -> requests.Response:
334
+ headers = kwargs.pop("headers", {})
335
+ headers.setdefault("Authorization", f"Bearer {token}")
336
+ headers.setdefault("Accept", "application/vnd.github+json")
337
+ headers.setdefault("User-Agent", "parrot-gittoolkit")
338
+ response = requests.request(method, url, headers=headers, timeout=30, **kwargs)
339
+ if response.status_code != expected:
340
+ raise GitToolkitError(
341
+ f"GitHub API call to {url} failed with status {response.status_code}: {response.text}"
342
+ )
343
+ return response
344
+
345
+ @staticmethod
346
+ def _encode_content(change: GitHubFileChange) -> Optional[str]:
347
+ if change.change_type == "delete":
348
+ return None
349
+ if change.encoding == "base64":
350
+ return change.content or ""
351
+ assert change.encoding == "utf-8"
352
+ data = (change.content or "").encode("utf-8")
353
+ return base64.b64encode(data).decode("ascii")
354
+
355
+ def _fetch_file_sha(
356
+ self,
357
+ ctx: _GitHubContext,
358
+ path: str,
359
+ ref: str,
360
+ token: str,
361
+ ) -> Optional[str]:
362
+ url = f"https://api.github.com/repos/{ctx.repository}/contents/{path}"
363
+ response = requests.get(
364
+ url,
365
+ headers={
366
+ "Authorization": f"Bearer {token}",
367
+ "Accept": "application/vnd.github+json",
368
+ "User-Agent": "parrot-gittoolkit",
369
+ },
370
+ params={"ref": ref},
371
+ timeout=30,
372
+ )
373
+ if response.status_code == 404:
374
+ return None
375
+ if response.status_code != 200:
376
+ raise GitToolkitError(
377
+ f"Unable to fetch metadata for {path}: {response.status_code} {response.text}"
378
+ )
379
+ payload = response.json()
380
+ return payload.get("sha")
381
+
382
+ def _create_pull_request_sync(
383
+ self,
384
+ *,
385
+ repository: Optional[str],
386
+ title: str,
387
+ body: Optional[str],
388
+ base_branch: Optional[str],
389
+ head_branch: Optional[str],
390
+ commit_message: Optional[str],
391
+ files: List[GitHubFileChange],
392
+ draft: bool,
393
+ labels: Optional[List[str]],
394
+ ) -> Dict[str, Any]:
395
+ ctx = self._prepare_github_context(repository, base_branch)
396
+ token = ctx.token
397
+ base_branch_name = ctx.base_branch
398
+
399
+ branch_name = head_branch or f"parrot/{_dt.datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
400
+ commit_message = commit_message or title
401
+
402
+ base_ref_url = f"https://api.github.com/repos/{ctx.repository}/git/ref/heads/{base_branch_name}"
403
+ ref_response = self._request("GET", base_ref_url, token, expected=200)
404
+ base_sha = ref_response.json()["object"]["sha"]
405
+
406
+ create_ref_url = f"https://api.github.com/repos/{ctx.repository}/git/refs"
407
+ payload = {"ref": f"refs/heads/{branch_name}", "sha": base_sha}
408
+ try:
409
+ self._request("POST", create_ref_url, token, expected=201, json=payload)
410
+ except GitToolkitError as exc:
411
+ if "Reference already exists" not in str(exc):
412
+ raise
413
+
414
+ for change in files:
415
+ sha = self._fetch_file_sha(ctx, change.path, branch_name, token)
416
+
417
+ if change.change_type == "delete":
418
+ if not sha:
419
+ raise GitToolkitError(
420
+ f"Cannot delete {change.path}: file does not exist in branch {branch_name}."
421
+ )
422
+ url = f"https://api.github.com/repos/{ctx.repository}/contents/{change.path}"
423
+ json_payload = {
424
+ "message": change.message or commit_message,
425
+ "branch": branch_name,
426
+ "sha": sha,
427
+ }
428
+ self._request("DELETE", url, token, expected=200, json=json_payload)
429
+ continue
430
+
431
+ encoded = self._encode_content(change)
432
+ if change.change_type == "modify" and not sha:
433
+ raise GitToolkitError(
434
+ f"Cannot modify {change.path}: file does not exist in branch {branch_name}."
435
+ )
436
+
437
+ url = f"https://api.github.com/repos/{ctx.repository}/contents/{change.path}"
438
+ json_payload = {
439
+ "message": change.message or commit_message,
440
+ "content": encoded,
441
+ "branch": branch_name,
442
+ }
443
+ if sha:
444
+ json_payload["sha"] = sha
445
+ self._request("PUT", url, token, expected=201 if not sha else 200, json=json_payload)
446
+
447
+ pr_url = f"https://api.github.com/repos/{ctx.repository}/pulls"
448
+ pr_payload = {
449
+ "title": title,
450
+ "body": body or "",
451
+ "head": branch_name,
452
+ "base": base_branch_name,
453
+ "draft": draft,
454
+ }
455
+ pr_response = self._request("POST", pr_url, token, expected=201, json=pr_payload)
456
+ pr_data = pr_response.json()
457
+
458
+ if labels:
459
+ labels_url = f"https://api.github.com/repos/{ctx.repository}/issues/{pr_data['number']}/labels"
460
+ self._request("POST", labels_url, token, expected=200, json={"labels": labels})
461
+
462
+ return {
463
+ "html_url": pr_data.get("html_url"),
464
+ "number": pr_data.get("number"),
465
+ "head_branch": branch_name,
466
+ "base_branch": base_branch_name,
467
+ "commits": len(files),
468
+ }
469
+
470
+ @tool_schema(CreatePullRequestInput)
471
+ async def create_pull_request(
472
+ self,
473
+ repository: Optional[str],
474
+ title: str,
475
+ body: Optional[str],
476
+ base_branch: Optional[str],
477
+ head_branch: Optional[str],
478
+ commit_message: Optional[str],
479
+ files: List[GitHubFileChange],
480
+ draft: bool = False,
481
+ labels: Optional[List[str]] = None,
482
+ ) -> Dict[str, Any]:
483
+ """Create a GitHub pull request with the supplied file updates."""
484
+
485
+ return await asyncio.to_thread(
486
+ self._create_pull_request_sync,
487
+ repository=repository,
488
+ title=title,
489
+ body=body,
490
+ base_branch=base_branch,
491
+ head_branch=head_branch,
492
+ commit_message=commit_message,
493
+ files=files,
494
+ draft=draft,
495
+ labels=labels,
496
+ )
497
+
498
+
499
+ __all__ = [
500
+ "GitToolkit",
501
+ "GitToolkitInput",
502
+ "GitPatchFile",
503
+ "GitHubFileChange",
504
+ "GeneratePatchInput",
505
+ "CreatePullRequestInput",
506
+ "GitToolkitError",
507
+ ]
508
+
@@ -0,0 +1,18 @@
1
+ from .tools import (
2
+ GoogleSearchTool,
3
+ GoogleSiteSearchTool,
4
+ GoogleLocationTool,
5
+ GoogleRoutesTool,
6
+ GoogleReviewsTool,
7
+ GoogleTrafficTool,
8
+ )
9
+ from .base import GoogleBaseTool
10
+
11
+ __all__ = (
12
+ "GoogleSearchTool",
13
+ "GoogleSiteSearchTool",
14
+ "GoogleLocationTool",
15
+ "GoogleRoutesTool",
16
+ "GoogleReviewsTool",
17
+ "GoogleTrafficTool",
18
+ )
@@ -0,0 +1,169 @@
1
+ """Base classes for Google Workspace tools."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ from typing import Dict, Any, Optional, Type, Union, List, Callable
6
+ from abc import abstractmethod
7
+
8
+ from pydantic import BaseModel, Field
9
+ from navconfig.logging import logging
10
+
11
+ from ..abstract import AbstractTool, AbstractToolArgsSchema
12
+ from ...interfaces.google import GoogleClient
13
+
14
+
15
+ class GoogleAuthMode:
16
+ """Authentication modes available for Google tools."""
17
+
18
+ SERVICE_ACCOUNT = "service_account"
19
+ USER = "user"
20
+ CACHED = "cached"
21
+
22
+
23
+ class GoogleToolArgsSchema(AbstractToolArgsSchema):
24
+ """Base schema for Google tool arguments."""
25
+
26
+ auth_mode: Optional[str] = Field(
27
+ default=None,
28
+ description="Authentication mode: 'service_account', 'user', or 'cached'."
29
+ )
30
+ scopes: Optional[Union[str, List[str]]] = Field(
31
+ default=None,
32
+ description="Optional override of the default scopes for this tool execution."
33
+ )
34
+
35
+
36
+ class GoogleBaseTool(AbstractTool):
37
+ """Base class for Google Workspace tools leveraging :class:`GoogleClient`."""
38
+
39
+ name: str = "google_base"
40
+ description: str = "Base Google Workspace tool"
41
+ args_schema: Type[BaseModel] = GoogleToolArgsSchema
42
+ return_direct: bool = False
43
+
44
+ def __init__(
45
+ self,
46
+ credentials: Optional[Union[str, Dict[str, Any], Path]] = None,
47
+ default_auth_mode: str = GoogleAuthMode.SERVICE_ACCOUNT,
48
+ scopes: Optional[Union[str, List[str]]] = None,
49
+ user_creds_cache_file: Optional[Union[str, Path]] = None,
50
+ open_browser: bool = True,
51
+ login_callback: Optional[Callable[[str], Optional[bool]]] = None,
52
+ interactive_login_kwargs: Optional[Dict[str, Any]] = None,
53
+ interactive_timeout: int = 300,
54
+ **kwargs
55
+ ):
56
+ super().__init__(**kwargs)
57
+
58
+ self.credentials = credentials
59
+ self.default_auth_mode = default_auth_mode
60
+ self.scopes = scopes if scopes is not None else 'all'
61
+ self.user_creds_cache_file = (
62
+ Path(user_creds_cache_file).expanduser().resolve()
63
+ if isinstance(user_creds_cache_file, (str, Path))
64
+ else None
65
+ )
66
+ self.open_browser = open_browser
67
+ self.login_callback = login_callback
68
+ self.interactive_login_kwargs = interactive_login_kwargs or {}
69
+ self.interactive_timeout = interactive_timeout
70
+
71
+ self._client_cache: Dict[str, GoogleClient] = {}
72
+ self.logger = logging.getLogger(f'Parrot.Tools.{self.__class__.__name__}')
73
+
74
+ async def _execute(self, **kwargs) -> Any: # type: ignore[override]
75
+ auth_mode = kwargs.pop('auth_mode', None)
76
+ scopes_override = kwargs.pop('scopes', None)
77
+ client = await self._get_client(auth_mode=auth_mode, scopes=scopes_override)
78
+ return await self._execute_google_operation(client=client, **kwargs)
79
+
80
+ async def _get_client(
81
+ self,
82
+ auth_mode: Optional[str] = None,
83
+ scopes: Optional[Union[str, List[str]]] = None
84
+ ) -> GoogleClient:
85
+ """Create or reuse a configured :class:`GoogleClient`."""
86
+
87
+ resolved_auth_mode = auth_mode or self.default_auth_mode
88
+ resolved_scopes = scopes if scopes is not None else self.scopes
89
+ cache_key = self._build_cache_key(resolved_auth_mode, resolved_scopes)
90
+
91
+ if cache_key in self._client_cache:
92
+ return self._client_cache[cache_key]
93
+
94
+ client_credentials: Optional[Union[str, Dict[str, Any], Path]]
95
+ if isinstance(self.credentials, dict):
96
+ client_credentials = self.credentials.copy()
97
+ else:
98
+ client_credentials = self.credentials
99
+
100
+ client_kwargs: Dict[str, Any] = {}
101
+ if self.user_creds_cache_file is not None:
102
+ client_kwargs['user_creds_cache_file'] = self.user_creds_cache_file
103
+
104
+ client = GoogleClient(
105
+ credentials=client_credentials,
106
+ scopes=resolved_scopes,
107
+ **client_kwargs
108
+ )
109
+
110
+ if resolved_auth_mode == GoogleAuthMode.SERVICE_ACCOUNT:
111
+ await client.initialize()
112
+ elif resolved_auth_mode == GoogleAuthMode.USER:
113
+ await client.interactive_login(
114
+ scopes=resolved_scopes,
115
+ open_browser=self.open_browser,
116
+ login_callback=self.login_callback,
117
+ timeout=self.interactive_timeout,
118
+ **self.interactive_login_kwargs
119
+ )
120
+ await client.initialize()
121
+ elif resolved_auth_mode == GoogleAuthMode.CACHED:
122
+ try:
123
+ await client.initialize()
124
+ except RuntimeError as auth_error:
125
+ if "User credentials not available" not in str(auth_error):
126
+ raise
127
+ self.logger.info(
128
+ "No cached Google credentials found; launching interactive login"
129
+ )
130
+ await client.interactive_login(
131
+ scopes=resolved_scopes,
132
+ open_browser=self.open_browser,
133
+ login_callback=self.login_callback,
134
+ timeout=self.interactive_timeout,
135
+ **self.interactive_login_kwargs
136
+ )
137
+ await client.initialize()
138
+ else:
139
+ raise ValueError(f"Unsupported Google auth mode: {resolved_auth_mode}")
140
+
141
+ self._client_cache[cache_key] = client
142
+ return client
143
+
144
+ def _build_cache_key(
145
+ self,
146
+ auth_mode: str,
147
+ scopes: Optional[Union[str, List[str]]]
148
+ ) -> str:
149
+ normalized_scopes = self._normalize_scope_list(scopes)
150
+ scope_fragment = ','.join(sorted(normalized_scopes)) if normalized_scopes else 'default'
151
+ return f"{auth_mode}:{scope_fragment}"
152
+
153
+ @staticmethod
154
+ def _normalize_scope_list(scopes: Optional[Union[str, List[str]]]) -> List[str]:
155
+ if scopes is None:
156
+ return []
157
+ return [scopes] if isinstance(scopes, str) else list(scopes)
158
+
159
+ def clear_client_cache(self) -> None:
160
+ """Clear cached client instances."""
161
+ self._client_cache.clear()
162
+
163
+ @abstractmethod
164
+ async def _execute_google_operation(
165
+ self,
166
+ client: GoogleClient,
167
+ **kwargs
168
+ ) -> Any:
169
+ """Execute the tool-specific Google API operation."""