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,442 @@
1
+ # parrot/tools/shell_tool.py
2
+ from __future__ import annotations
3
+ from typing import Any, Dict, List, Optional, Union
4
+ import asyncio
5
+ import os
6
+ import re
7
+ import json
8
+ from ..abstract import AbstractTool
9
+ from .models import CommandObject, ShellToolArgs, PlanStep
10
+ from .actions import (
11
+ BaseAction,
12
+ ActionResult,
13
+ RunCommand,
14
+ ExecFile,
15
+ ListFiles,
16
+ ReadFile,
17
+ WriteFile,
18
+ DeleteFile,
19
+ CopyFile,
20
+ MoveFile,
21
+ CheckExists
22
+ )
23
+ from .engine import EvalAction
24
+
25
+ class ShellTool(AbstractTool):
26
+ """
27
+ Interactive Shell tool with optional PTY support.
28
+
29
+ Features:
30
+ - Accepts single string, list of strings, or list of command objects
31
+ - Plan-mode (tiny sequential DAG) with `uses` and templating
32
+ - Sequential or parallel execution
33
+ - Per-command and global timeouts
34
+ - Global and per-command work_dir, env
35
+ - Optional PTY mode for interactive programs (merged stdout/stderr)
36
+ - Live output callback hook
37
+ """
38
+ name: str = "shell"
39
+ description: str = "Execute shell commands with optional PTY, sequential/parallel control, and rich command objects."
40
+ args_schema = ShellToolArgs
41
+
42
+ def _default_output_dir(self):
43
+ # This tool doesn't produce files by default
44
+ return None
45
+
46
+ async def _execute(
47
+ self,
48
+ command: Optional[Union[str, List[Union[str, Dict[str, Any]]]]] = None,
49
+ plan: Optional[List[Dict[str, Any]]] = None,
50
+ parallel: bool = False,
51
+ ignore_errors: bool = False,
52
+ timeout: Optional[int] = None,
53
+ work_dir: Optional[str] = None,
54
+ pty: bool = False,
55
+ env: Optional[Dict[str, str]] = None,
56
+ non_interactive: bool = False
57
+ ) -> Dict[str, Any]:
58
+ if plan:
59
+ return await self._run_plan(
60
+ plan=plan,
61
+ ignore_errors=ignore_errors,
62
+ timeout=timeout,
63
+ work_dir=work_dir or os.getcwd(),
64
+ pty=pty,
65
+ env=env or {},
66
+ non_interactive=non_interactive
67
+ )
68
+ if command is not None:
69
+ return await self._run_commands(
70
+ command=command,
71
+ parallel=parallel,
72
+ ignore_errors=ignore_errors,
73
+ timeout=timeout,
74
+ work_dir=work_dir or os.getcwd(),
75
+ pty=pty,
76
+ env=env or {},
77
+ non_interactive=non_interactive
78
+ )
79
+ return {"ok": True, "results": []}
80
+
81
+ # ---- helpers ----
82
+ def _normalize_to_objects(
83
+ self,
84
+ command: Union[str, List[Union[str, CommandObject, Dict[str, Any]]]]
85
+ ) -> List[CommandObject]:
86
+ if isinstance(command, str):
87
+ return [CommandObject(command=command)]
88
+ objs: List[CommandObject] = []
89
+ for item in command:
90
+ if isinstance(item, str):
91
+ objs.append(CommandObject(command=item))
92
+ elif isinstance(item, dict):
93
+ objs.append(CommandObject(**item))
94
+ elif isinstance(item, CommandObject):
95
+ objs.append(item)
96
+ else:
97
+ raise ValueError(f"Unsupported command element: {type(item)}")
98
+ return objs
99
+
100
+ def _live_cb(self, line: str, is_stderr: bool, cmd: str):
101
+ try:
102
+ self.logger.debug(f"[{cmd}] {'STDERR' if is_stderr else 'STDOUT'}: {line.rstrip()}")
103
+ except Exception:
104
+ pass
105
+
106
+ # ---- classic command mode ----
107
+ async def _run_commands(
108
+ self,
109
+ *,
110
+ command: Union[str, List[Union[str, Dict[str, Any]]]],
111
+ parallel: bool,
112
+ ignore_errors: bool,
113
+ timeout: Optional[int],
114
+ work_dir: str,
115
+ pty: bool,
116
+ env: Dict[str, str],
117
+ non_interactive: bool
118
+ ) -> Dict[str, Any]:
119
+ cmds = self._normalize_to_objects(command)
120
+ actions = [
121
+ self._make_action_from_cmdobj(
122
+ c, timeout, work_dir, env, pty, non_interactive, ignore_errors
123
+ ) for c in cmds
124
+ ]
125
+
126
+ results: List[ActionResult] = []
127
+ if parallel and len(actions) > 1:
128
+ results = await asyncio.gather(*[a.run() for a in actions])
129
+ else:
130
+ for act in actions:
131
+ r = await act.run()
132
+ results.append(r)
133
+ if not r.ok and not (ignore_errors or act.ignore_errors):
134
+ break
135
+
136
+ payload = [self._result_to_dict(r) for r in results]
137
+ overall_ok = all(item["ok"] for item in payload) if payload else True
138
+ return {"ok": overall_ok, "parallel": parallel, "results": payload}
139
+
140
+ def _make_action_from_cmdobj(
141
+ self,
142
+ spec: CommandObject,
143
+ default_timeout: Optional[int],
144
+ base_work_dir: str,
145
+ base_env: Dict[str, str],
146
+ tool_level_pty: bool,
147
+ tool_level_non_interactive: bool,
148
+ tool_level_ignore_errors: bool
149
+ ) -> BaseAction:
150
+ timeout = spec.timeout if spec.timeout is not None else default_timeout
151
+ work_dir = spec.work_dir or base_work_dir
152
+ env = dict(base_env)
153
+ if spec.env:
154
+ env.update(spec.env)
155
+ pty_mode = tool_level_pty if spec.pty is None else bool(spec.pty)
156
+ non_interactive = tool_level_non_interactive if spec.non_interactive is None else bool(spec.non_interactive)
157
+ ignore_errors = tool_level_ignore_errors if spec.ignore_errors is None else bool(spec.ignore_errors)
158
+
159
+ raw = spec.command.strip()
160
+ if raw.startswith("ls") or raw == "ls":
161
+ return ListFiles(
162
+ type_name="list_files", cmd=raw, work_dir=work_dir, timeout=timeout,
163
+ env=env, pty_mode=pty_mode, stdin_lines=spec.stdin or [],
164
+ non_interactive=non_interactive, ignore_errors=ignore_errors,
165
+ live_callback=self._live_cb
166
+ )
167
+ elif raw.endswith(".sh") or raw.startswith("./") or raw.startswith("/"):
168
+ return ExecFile(
169
+ type_name="exec_file", cmd=raw, work_dir=work_dir, timeout=timeout,
170
+ env=env, pty_mode=pty_mode, stdin_lines=spec.stdin or [],
171
+ non_interactive=non_interactive, ignore_errors=ignore_errors,
172
+ live_callback=self._live_cb
173
+ )
174
+ else:
175
+ return RunCommand(
176
+ type_name="run_command", cmd=raw, work_dir=work_dir, timeout=timeout,
177
+ env=env, pty_mode=pty_mode, stdin_lines=spec.stdin or [],
178
+ non_interactive=non_interactive, ignore_errors=ignore_errors,
179
+ live_callback=self._live_cb
180
+ )
181
+
182
+ def _result_to_dict(self, r: ActionResult) -> Dict[str, Any]:
183
+ return {
184
+ "type": r.type,
185
+ "cmd": r.cmd,
186
+ "work_dir": r.work_dir,
187
+ "ok": r.ok,
188
+ "exit_code": r.exit_code,
189
+ "timed_out": r.timed_out,
190
+ "duration": round(r.duration, 4),
191
+ "stdout": r.stdout,
192
+ "stderr": r.stderr,
193
+ "metadata": r.metadata
194
+ }
195
+
196
+ # ---- plan mode ----
197
+ async def _run_plan(
198
+ self,
199
+ *,
200
+ plan: List[Dict[str, Any]],
201
+ ignore_errors: bool,
202
+ timeout: Optional[int],
203
+ work_dir: str,
204
+ pty: bool,
205
+ env: Dict[str, str],
206
+ non_interactive: bool
207
+ ) -> Dict[str, Any]:
208
+ steps = [PlanStep(**s) if not isinstance(s, PlanStep) else s for s in plan]
209
+ results: List[ActionResult] = []
210
+ base_ctx = {"results": results} # live context for templating / uses
211
+
212
+ for idx, step in enumerate(steps):
213
+ # Resolve per-step overrides
214
+ step_timeout = step.timeout if step.timeout is not None else timeout
215
+ step_work_dir = step.work_dir or work_dir
216
+ step_env = dict(env)
217
+ if step.env:
218
+ step_env.update(step.env)
219
+ step_pty = pty if step.pty is None else bool(step.pty)
220
+ step_non_interactive = non_interactive if step.non_interactive is None else bool(step.non_interactive)
221
+ step_ignore = ignore_errors if step.ignore_errors is None else bool(step.ignore_errors)
222
+
223
+ # Prepare context for templating / uses
224
+ src = self._resolve_uses(step.uses, results) if step.uses else None
225
+ rendered = self._render_template(step.template, src, results) if step.template else None
226
+
227
+ # Build action by type
228
+ action: BaseAction
229
+ if step.type in ("run_command", "exec_file", "list_files"):
230
+ # unify into command list
231
+ command_spec = step.command or rendered or ""
232
+ objs = self._normalize_to_objects(command_spec)
233
+ # Only one command allowed per plan step for now to keep step/result alignment clean
234
+ if len(objs) != 1:
235
+ raise ValueError(f"Plan step {idx} expects exactly one command; got {len(objs)}.")
236
+ action = self._make_action_from_cmdobj(
237
+ objs[0], step_timeout, step_work_dir, step_env, step_pty, step_non_interactive, step_ignore
238
+ )
239
+ # Force type to requested for accurate step labeling
240
+ action.type_name = step.type
241
+
242
+ elif step.type == "check_exists":
243
+ target = (step.path or rendered or "").strip()
244
+ action = CheckExists(
245
+ type_name="check_exists",
246
+ cmd=target,
247
+ work_dir=step_work_dir,
248
+ ignore_errors=step_ignore
249
+ )
250
+
251
+ elif step.type == "read_file":
252
+ target = (step.path or rendered or "").strip()
253
+ rf = ReadFile(
254
+ type_name="read_file",
255
+ cmd=target,
256
+ work_dir=step_work_dir,
257
+ ignore_errors=step_ignore
258
+ )
259
+ # attach options
260
+ setattr(rf, "_max_bytes", step.max_bytes)
261
+ setattr(rf, "_encoding", step.encoding or "utf-8")
262
+ action = rf
263
+ elif step.type == "write_file":
264
+ target = (step.path or rendered or "").strip()
265
+ if not target:
266
+ raise ValueError(f"Plan step {idx}: write_file requires 'path'.")
267
+ content_src: Any = rendered if rendered is not None else (src if src is not None else step.content or "")
268
+ # If content is not a string, JSON-dump it for safety
269
+ if not isinstance(content_src, str):
270
+ try:
271
+ content_str = json.dumps(content_src, ensure_ascii=False)
272
+ except Exception:
273
+ content_str = str(content_src)
274
+ else:
275
+ content_str = content_src
276
+
277
+ action = WriteFile(
278
+ path=target,
279
+ content=content_str,
280
+ encoding=step.encoding or "utf-8",
281
+ append=bool(step.append),
282
+ make_dirs=bool(step.make_dirs if step.make_dirs is not None else True),
283
+ overwrite=bool(step.overwrite if step.overwrite is not None else True),
284
+ work_dir=step_work_dir,
285
+ ignore_errors=step_ignore
286
+ )
287
+ elif step.type == "delete_file":
288
+ target = (step.path or rendered or "").strip()
289
+ if not target:
290
+ raise ValueError(f"Plan step {idx}: delete_file requires 'path'.")
291
+ action = DeleteFile(
292
+ path=target,
293
+ recursive=bool(step.recursive),
294
+ missing_ok=bool(step.missing_ok if step.missing_ok is not None else True),
295
+ work_dir=step_work_dir,
296
+ ignore_errors=step_ignore
297
+ )
298
+ elif step.type == "copy_file":
299
+ src = (step.path or rendered or "").strip()
300
+ dest = (step.dest or "").strip()
301
+ if not src or not dest:
302
+ raise ValueError(f"Plan step {idx}: copy_file requires 'path' (src) and 'dest'.")
303
+ action = CopyFile(
304
+ src=src,
305
+ dest=dest,
306
+ recursive=bool(step.recursive),
307
+ overwrite=bool(step.overwrite if step.overwrite is not None else True),
308
+ make_dirs=bool(step.make_dirs if step.make_dirs is not None else True),
309
+ work_dir=step_work_dir,
310
+ ignore_errors=step_ignore
311
+ )
312
+ elif step.type == "move_file":
313
+ src = (step.path or rendered or "").strip()
314
+ dest = (step.dest or "").strip()
315
+ if not src or not dest:
316
+ raise ValueError(f"Plan step {idx}: move_file requires 'path' (src) and 'dest'.")
317
+ action = MoveFile(
318
+ src=src,
319
+ dest=dest,
320
+ recursive=bool(step.recursive if step.recursive is not None else True),
321
+ overwrite=bool(step.overwrite if step.overwrite is not None else True),
322
+ make_dirs=bool(step.make_dirs if step.make_dirs is not None else True),
323
+ work_dir=step_work_dir,
324
+ ignore_errors=step_ignore
325
+ )
326
+ elif step.type == "eval":
327
+ eval_src: Any = rendered if rendered is not None else src
328
+ action = EvalAction(
329
+ eval_type=step.eval_type or "regex",
330
+ expr=step.expr or "",
331
+ group=step.group,
332
+ src_text_or_obj=eval_src,
333
+ as_json=bool(step.as_json),
334
+ work_dir=step_work_dir,
335
+ ignore_errors=step_ignore
336
+ )
337
+
338
+ else:
339
+ raise ValueError(
340
+ f"Unsupported plan step type: {step.type}"
341
+ )
342
+
343
+ # Run step
344
+ r = await action.run()
345
+ results.append(r)
346
+ if not r.ok and not (ignore_errors or step_ignore):
347
+ break
348
+
349
+ payload = [self._result_to_dict(r) for r in results]
350
+ overall_ok = all(item["ok"] for item in payload) if payload else True
351
+ return {"ok": overall_ok, "results": payload}
352
+
353
+ # ---- uses & templating ----
354
+ def _resolve_uses(self, uses: str, results: List[ActionResult]) -> Any:
355
+ """
356
+ Accepts strings like:
357
+ - "prev.stdout"
358
+ - "prev"
359
+ - "result[0].stdout"
360
+ - "result[-1]"
361
+ Returns the object or string (stdout) referenced.
362
+ """
363
+ uses = uses.strip()
364
+ if uses.startswith("prev"):
365
+ base = results[-1] if results else None
366
+ if base is None:
367
+ return ""
368
+ if uses == "prev":
369
+ return self._ar_to_src(base)
370
+ elif uses == "prev.stdout":
371
+ return base.stdout
372
+ elif uses == "prev.stderr":
373
+ return base.stderr
374
+ else:
375
+ return self._attr_path(self._ar_to_src(base), uses[len("prev."):])
376
+ m = re.match(r"result\[(\-?\d+)\](?:\.(stdout|stderr))?$", uses)
377
+ if m:
378
+ i = int(m.group(1))
379
+ if not results:
380
+ return ""
381
+ try:
382
+ base = results[i]
383
+ except IndexError:
384
+ return ""
385
+ stream = m.group(2)
386
+ if stream == "stdout":
387
+ return base.stdout
388
+ if stream == "stderr":
389
+ return base.stderr
390
+ return self._ar_to_src(base)
391
+ # Fallback: direct literal
392
+ return uses
393
+
394
+ @staticmethod
395
+ def _ar_to_src(ar: ActionResult) -> Dict[str, Any]:
396
+ return {
397
+ "type": ar.type,
398
+ "stdout": ar.stdout,
399
+ "stderr": ar.stderr,
400
+ "ok": ar.ok,
401
+ "exit_code": ar.exit_code,
402
+ "metadata": ar.metadata,
403
+ "cmd": ar.cmd,
404
+ "work_dir": ar.work_dir
405
+ }
406
+
407
+ @staticmethod
408
+ def _attr_path(obj: Any, path: str) -> Any:
409
+ # Just a minimal dot path for dicts
410
+ cur = obj
411
+ for part in path.split("."):
412
+ if isinstance(cur, dict) and part in cur:
413
+ cur = cur[part]
414
+ else:
415
+ return ""
416
+ return cur
417
+
418
+ def _render_template(self, template: str, src: Any, results: List[ActionResult]) -> str:
419
+ """
420
+ Tiny Jinja-lite:
421
+ - {{ prev.stdout }}
422
+ - {{ result[-1].stdout }}
423
+ - {{ json(results) }} # dumps
424
+ """
425
+ out = template
426
+
427
+ def repl_prev(m):
428
+ key = m.group(1).strip()
429
+ if key == "prev":
430
+ return json.dumps(self._ar_to_src(results[-1])) if results else ""
431
+ if key.startswith("prev."):
432
+ return str(self._resolve_uses(key, results) or "")
433
+ if key.startswith("result["):
434
+ return str(self._resolve_uses(key, results) or "")
435
+ if key == "json(results)":
436
+ return json.dumps([self._ar_to_src(r) for r in results], ensure_ascii=False)
437
+ if key == "src":
438
+ return json.dumps(src) if isinstance(src, (dict, list)) else (src or "")
439
+ return ""
440
+ # {{ ... }}
441
+ out = re.sub(r"\{\{\s*(.*?)\s*\}\}", repl_prev, out)
442
+ return out
@@ -0,0 +1,214 @@
1
+ """SiteSearch tool for site-specific crawling with markdown output."""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import tempfile
6
+ import urllib.parse
7
+ from pathlib import Path
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ import aiohttp
11
+ from bs4 import BeautifulSoup
12
+ from markitdown import MarkItDown
13
+ from pydantic import BaseModel, Field
14
+
15
+ from .google.tools import GoogleSiteSearchTool
16
+ from .scraping.driver import SeleniumSetup
17
+
18
+
19
+ class SiteSearchArgs(BaseModel):
20
+ """Arguments schema for :class:`SiteSearch`."""
21
+
22
+ url: str = Field(
23
+ description="Base URL of the site to explore (e.g., https://www.statista.com/)",
24
+ )
25
+ query: str = Field(
26
+ description="Terms to search for within the provided site",
27
+ )
28
+ selectors: Optional[List[str]] = Field(
29
+ default=None,
30
+ description="Optional CSS selectors to extract specific page areas after rendering",
31
+ )
32
+ max_results: int = Field(
33
+ default=3,
34
+ ge=1,
35
+ le=10,
36
+ description="Maximum number of search results to process",
37
+ )
38
+
39
+
40
+ class SiteSearch(GoogleSiteSearchTool):
41
+ """Perform Google-powered site searches and return rendered content as markdown."""
42
+
43
+ name = "site_search"
44
+ description = (
45
+ "Search within a given site and return fully-rendered page content as markdown, "
46
+ "including PDF conversion when encountered."
47
+ )
48
+ args_schema = SiteSearchArgs
49
+
50
+ def __init__(self, **kwargs: Any) -> None:
51
+ super().__init__(**kwargs)
52
+ self._markitdown = MarkItDown()
53
+ self._selenium_setup: Optional[SeleniumSetup] = None
54
+ self._driver = None
55
+
56
+ async def _execute(
57
+ self,
58
+ url: str,
59
+ query: str,
60
+ selectors: Optional[List[str]] = None,
61
+ max_results: int = 3,
62
+ **_: Any,
63
+ ) -> Dict[str, Any]:
64
+ site = self._extract_site(url)
65
+ if not site:
66
+ raise ValueError(f"Could not extract site from URL: {url}")
67
+
68
+ search_results = await super()._execute(
69
+ query=query,
70
+ site=site,
71
+ max_results=max_results,
72
+ preview=False,
73
+ preview_method="aiohttp",
74
+ )
75
+
76
+ processed_results = []
77
+ try:
78
+ for item in search_results.get("results", [])[:max_results]:
79
+ link = item.get("link")
80
+ if not link:
81
+ continue
82
+
83
+ if await self._is_pdf(link):
84
+ markdown, content_type = await self._convert_pdf(link)
85
+ else:
86
+ markdown, content_type = await self._render_with_selenium(
87
+ link, selectors
88
+ )
89
+
90
+ processed_results.append(
91
+ {
92
+ "title": item.get("title"),
93
+ "url": link,
94
+ "snippet": item.get("snippet"),
95
+ "content_type": content_type,
96
+ "markdown": markdown,
97
+ }
98
+ )
99
+ finally:
100
+ await self._close_driver()
101
+
102
+ return {
103
+ "site": site,
104
+ "search_terms": query,
105
+ "total_results": len(processed_results),
106
+ "results": processed_results,
107
+ }
108
+
109
+ async def _render_with_selenium(
110
+ self, url: str, selectors: Optional[List[str]]
111
+ ) -> tuple[str, str]:
112
+ driver = await self._get_driver()
113
+ loop = asyncio.get_running_loop()
114
+
115
+ await loop.run_in_executor(None, driver.get, url)
116
+ await asyncio.sleep(2)
117
+ page_source = await loop.run_in_executor(None, lambda: driver.page_source)
118
+
119
+ soup = BeautifulSoup(page_source, "html.parser")
120
+ if selectors:
121
+ selected_html = []
122
+ for selector in selectors:
123
+ selected_html.extend([str(elem) for elem in soup.select(selector)])
124
+ html_content = "\n".join(selected_html) if selected_html else str(soup)
125
+ else:
126
+ html_content = str(soup)
127
+
128
+ markdown = await self._convert_html_to_markdown(html_content)
129
+ return markdown, "text/html"
130
+
131
+ async def _convert_pdf(self, url: str) -> tuple[str, str]:
132
+ timeout = aiohttp.ClientTimeout(total=60)
133
+ headers = {
134
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
135
+ "(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
136
+ }
137
+ async with aiohttp.ClientSession(timeout=timeout, headers=headers) as session:
138
+ async with session.get(url) as response:
139
+ if response.status != 200:
140
+ return (f"Failed to download PDF (HTTP {response.status})", "application/pdf")
141
+
142
+ pdf_bytes = await response.read()
143
+ loop = asyncio.get_running_loop()
144
+
145
+ def convert() -> str:
146
+ with tempfile.NamedTemporaryFile(
147
+ mode="wb", suffix=".pdf", delete=False
148
+ ) as tmp_file:
149
+ tmp_file.write(pdf_bytes)
150
+ tmp_path = Path(tmp_file.name)
151
+
152
+ try:
153
+ result = self._markitdown.convert(str(tmp_path))
154
+ markdown_content = getattr(result, "text_content", "") or ""
155
+ finally:
156
+ tmp_path.unlink(missing_ok=True)
157
+ return markdown_content
158
+
159
+ markdown_content = await loop.run_in_executor(None, convert)
160
+ return markdown_content, "application/pdf"
161
+
162
+ async def _convert_html_to_markdown(self, html: str) -> str:
163
+ loop = asyncio.get_running_loop()
164
+
165
+ def convert() -> str:
166
+ with tempfile.NamedTemporaryFile(
167
+ mode="w", suffix=".html", delete=False, encoding="utf-8"
168
+ ) as tmp_file:
169
+ tmp_file.write(html)
170
+ tmp_path = Path(tmp_file.name)
171
+
172
+ try:
173
+ result = self._markitdown.convert(str(tmp_path))
174
+ markdown_content = getattr(result, "text_content", "") or ""
175
+ finally:
176
+ tmp_path.unlink(missing_ok=True)
177
+
178
+ return markdown_content
179
+
180
+ return await loop.run_in_executor(None, convert)
181
+
182
+ async def _is_pdf(self, url: str) -> bool:
183
+ if url.lower().endswith(".pdf"):
184
+ return True
185
+
186
+ timeout = aiohttp.ClientTimeout(total=10)
187
+ try:
188
+ async with aiohttp.ClientSession(timeout=timeout) as session:
189
+ async with session.head(url, allow_redirects=True) as response:
190
+ content_type = response.headers.get("Content-Type", "").lower()
191
+ return "pdf" in content_type
192
+ except Exception:
193
+ return False
194
+
195
+ async def _get_driver(self):
196
+ if self._driver is None:
197
+ self._selenium_setup = SeleniumSetup()
198
+ self._driver = await self._selenium_setup.get_driver()
199
+ return self._driver
200
+
201
+ async def _close_driver(self):
202
+ if self._driver is not None:
203
+ loop = asyncio.get_running_loop()
204
+ await loop.run_in_executor(None, self._driver.quit)
205
+ self._driver = None
206
+ self._selenium_setup = None
207
+
208
+ @staticmethod
209
+ def _extract_site(url: str) -> str:
210
+ parsed = urllib.parse.urlparse(url)
211
+ return parsed.netloc or parsed.path
212
+
213
+
214
+ __all__ = ["SiteSearch", "SiteSearchArgs"]