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,527 @@
1
+ """
2
+ OpenWeather Tool migrated to use AbstractTool framework with aiohttp.
3
+ """
4
+ import os
5
+ from typing import Dict, Any, Optional, Literal
6
+ from pathlib import Path
7
+ import json
8
+ import asyncio
9
+ import aiohttp
10
+ from pydantic import BaseModel, Field, field_validator
11
+ from .abstract import AbstractTool, ToolResult
12
+
13
+
14
+ class OpenWeatherArgs(BaseModel):
15
+ """Arguments schema for OpenWeatherTool."""
16
+
17
+ latitude: float = Field(
18
+ ...,
19
+ description="The latitude of the location you want weather information about",
20
+ ge=-90.0,
21
+ le=90.0
22
+ )
23
+ longitude: float = Field(
24
+ ...,
25
+ description="The longitude of the location you want weather information about",
26
+ ge=-180.0,
27
+ le=180.0
28
+ )
29
+ request_type: Literal['weather', 'forecast'] = Field(
30
+ 'weather',
31
+ description="Type of weather information: 'weather' for current conditions, 'forecast' for future predictions"
32
+ )
33
+ units: Literal['metric', 'imperial', 'standard'] = Field(
34
+ 'imperial',
35
+ description="Temperature units: 'metric' (Celsius), 'imperial' (Fahrenheit), 'standard' (Kelvin)"
36
+ )
37
+ country: str = Field(
38
+ 'us',
39
+ description="Country code for the location (ISO 3166 country codes)"
40
+ )
41
+ forecast_days: int = Field(
42
+ 3,
43
+ description="Number of days for forecast (only used when request_type='forecast')",
44
+ ge=1,
45
+ le=16
46
+ )
47
+
48
+ @field_validator('country')
49
+ @classmethod
50
+ def validate_country(cls, v):
51
+ if len(v) != 2:
52
+ raise ValueError("Country code must be exactly 2 characters (ISO 3166)")
53
+ return v.lower()
54
+
55
+
56
+ class OpenWeatherTool(AbstractTool):
57
+ """
58
+ Tool to get weather information for specific locations using OpenWeatherMap API.
59
+
60
+ This tool provides current weather conditions and weather forecasts for any location
61
+ specified by latitude and longitude coordinates. It supports different temperature
62
+ units and can be configured for different countries.
63
+
64
+ Features:
65
+ - Current weather conditions
66
+ - Weather forecasts (up to 16 days)
67
+ - Multiple temperature units (Celsius, Fahrenheit, Kelvin)
68
+ - Country-specific formatting
69
+ - Comprehensive weather data including temperature, humidity, pressure, wind, etc.
70
+ """
71
+
72
+ name = "openweather_tool"
73
+ description = (
74
+ "Get current weather information or forecast for specific coordinates. "
75
+ "Supports current weather and forecasts with configurable units and country settings. "
76
+ "Requires latitude and longitude coordinates."
77
+ )
78
+ args_schema = OpenWeatherArgs
79
+
80
+ def __init__(
81
+ self,
82
+ api_key: Optional[str] = None,
83
+ default_units: str = 'imperial',
84
+ default_country: str = 'us',
85
+ default_request_type: str = 'weather',
86
+ timeout: int = 10,
87
+ **kwargs
88
+ ):
89
+ """
90
+ Initialize the OpenWeather tool.
91
+
92
+ Args:
93
+ api_key: OpenWeatherMap API key (if None, reads from OPENWEATHER_APPID env var)
94
+ default_units: Default temperature units ('metric', 'imperial', 'standard')
95
+ default_country: Default country code
96
+ default_request_type: Default request type ('weather' or 'forecast')
97
+ timeout: Request timeout in seconds
98
+ **kwargs: Additional arguments for AbstractTool
99
+ """
100
+ super().__init__(**kwargs)
101
+
102
+ # API configuration
103
+ self.api_key = api_key or os.getenv('OPENWEATHER_APPID')
104
+ if not self.api_key:
105
+ raise ValueError(
106
+ "OpenWeather API key must be provided or set in OPENWEATHER_APPID environment variable"
107
+ )
108
+
109
+ # Default settings
110
+ self.default_units = default_units
111
+ self.default_country = default_country
112
+ self.default_request_type = default_request_type
113
+ self.timeout = timeout
114
+
115
+ # API endpoints
116
+ self.base_url = "https://api.openweathermap.org/data/2.5"
117
+
118
+ self.logger.info(
119
+ f"OpenWeather tool initialized with defaults: units={default_units}, country={default_country}"
120
+ )
121
+
122
+ def _default_output_dir(self) -> Optional[Path]:
123
+ """Get the default output directory for weather data."""
124
+ return self.static_dir / "weather_data" if self.static_dir else None
125
+
126
+ def _build_url(
127
+ self,
128
+ latitude: float,
129
+ longitude: float,
130
+ request_type: str,
131
+ units: str,
132
+ forecast_days: int
133
+ ) -> str:
134
+ """
135
+ Build the appropriate API URL based on request type.
136
+
137
+ Args:
138
+ latitude: Latitude coordinate
139
+ longitude: Longitude coordinate
140
+ request_type: Type of request ('weather' or 'forecast')
141
+ units: Temperature units
142
+ forecast_days: Number of forecast days
143
+
144
+ Returns:
145
+ Complete API URL
146
+ """
147
+ base_params = f"lat={latitude}&lon={longitude}&units={units}&appid={self.api_key}"
148
+ if request_type == 'weather':
149
+ # Current weather - exclude minutely and hourly for cleaner response
150
+ url = f"{self.base_url}/weather?{base_params}&exclude=minutely,hourly"
151
+ elif request_type == 'forecast':
152
+ # For forecast, use the 5-day/3-hour forecast API
153
+ # cnt parameter limits the number of forecast entries (each is 3 hours)
154
+ cnt = min(forecast_days * 8, 40) # Max 40 entries (5 days * 8 intervals per day)
155
+ url = f"{self.base_url}/forecast?{base_params}&cnt={cnt}"
156
+ else:
157
+ raise ValueError(f"Invalid request type: {request_type}")
158
+ return url
159
+
160
+ def _format_weather_response(
161
+ self,
162
+ data: Dict[str, Any],
163
+ request_type: str,
164
+ units: str,
165
+ latitude: float,
166
+ longitude: float
167
+ ) -> Dict[str, Any]:
168
+ """
169
+ Format the weather API response for better usability.
170
+
171
+ Args:
172
+ data: Raw API response
173
+ request_type: Type of request
174
+ units: Temperature units used
175
+ latitude: Original latitude
176
+ longitude: Original longitude
177
+
178
+ Returns:
179
+ Formatted weather data
180
+ """
181
+ try:
182
+ # Determine temperature unit symbol
183
+ temp_unit = {
184
+ 'metric': '°C',
185
+ 'imperial': '°F',
186
+ 'standard': 'K'
187
+ }.get(units, '°F')
188
+
189
+ if request_type == 'weather':
190
+ # Format current weather
191
+ main = data.get('main', {})
192
+ weather = data.get('weather', [{}])[0]
193
+ wind = data.get('wind', {})
194
+ sys = data.get('sys', {})
195
+
196
+ formatted = {
197
+ 'location': {
198
+ 'latitude': latitude,
199
+ 'longitude': longitude,
200
+ 'city': data.get('name', 'Unknown'),
201
+ 'country': sys.get('country', 'Unknown')
202
+ },
203
+ 'current_weather': {
204
+ 'temperature': f"{main.get('temp', 'N/A')}{temp_unit}",
205
+ 'feels_like': f"{main.get('feels_like', 'N/A')}{temp_unit}",
206
+ 'temperature_min': f"{main.get('temp_min', 'N/A')}{temp_unit}",
207
+ 'temperature_max': f"{main.get('temp_max', 'N/A')}{temp_unit}",
208
+ 'description': weather.get('description', 'N/A').title(),
209
+ 'condition': weather.get('main', 'N/A'),
210
+ 'humidity': f"{main.get('humidity', 'N/A')}%",
211
+ 'pressure': f"{main.get('pressure', 'N/A')} hPa",
212
+ 'visibility': f"{data.get('visibility', 'N/A')} m" if 'visibility' in data else 'N/A'
213
+ },
214
+ 'wind': {
215
+ 'speed': f"{wind.get('speed', 'N/A')} {'mph' if units == 'imperial' else 'm/s'}",
216
+ 'direction': f"{wind.get('deg', 'N/A')}°" if 'deg' in wind else 'N/A',
217
+ 'gust': f"{wind.get('gust', 'N/A')} {'mph' if units == 'imperial' else 'm/s'}" if 'gust' in wind else 'N/A'
218
+ },
219
+ 'metadata': {
220
+ 'request_type': request_type,
221
+ 'units': units,
222
+ 'timestamp': data.get('dt'),
223
+ 'timezone': data.get('timezone'),
224
+ 'sunrise': sys.get('sunrise'),
225
+ 'sunset': sys.get('sunset')
226
+ }
227
+ }
228
+
229
+ else: # forecast
230
+ # Format forecast data
231
+ forecasts = []
232
+ for item in data.get('list', []):
233
+ main = item.get('main', {})
234
+ weather = item.get('weather', [{}])[0]
235
+ wind = item.get('wind', {})
236
+
237
+ forecast_item = {
238
+ 'datetime': item.get('dt_txt', 'N/A'),
239
+ 'timestamp': item.get('dt'),
240
+ 'temperature': f"{main.get('temp', 'N/A')}{temp_unit}",
241
+ 'feels_like': f"{main.get('feels_like', 'N/A')}{temp_unit}",
242
+ 'temperature_min': f"{main.get('temp_min', 'N/A')}{temp_unit}",
243
+ 'temperature_max': f"{main.get('temp_max', 'N/A')}{temp_unit}",
244
+ 'description': weather.get('description', 'N/A').title(),
245
+ 'condition': weather.get('main', 'N/A'),
246
+ 'humidity': f"{main.get('humidity', 'N/A')}%",
247
+ 'pressure': f"{main.get('pressure', 'N/A')} hPa",
248
+ 'wind_speed': f"{wind.get('speed', 'N/A')} {'mph' if units == 'imperial' else 'm/s'}",
249
+ 'precipitation_probability': f"{item.get('pop', 0) * 100:.0f}%" if 'pop' in item else 'N/A'
250
+ }
251
+ forecasts.append(forecast_item)
252
+
253
+ city = data.get('city', {})
254
+ formatted = {
255
+ 'location': {
256
+ 'latitude': latitude,
257
+ 'longitude': longitude,
258
+ 'city': city.get('name', 'Unknown'),
259
+ 'country': city.get('country', 'Unknown')
260
+ },
261
+ 'forecast': forecasts,
262
+ 'forecast_summary': {
263
+ 'total_entries': len(forecasts),
264
+ 'forecast_period': f"{forecasts[0]['datetime']} to {forecasts[-1]['datetime']}" if forecasts else 'N/A'
265
+ },
266
+ 'metadata': {
267
+ 'request_type': request_type,
268
+ 'units': units,
269
+ 'timezone': city.get('timezone')
270
+ }
271
+ }
272
+
273
+ return formatted
274
+
275
+ except Exception as e:
276
+ self.logger.error(f"Error formatting weather response: {e}")
277
+ # Return raw data if formatting fails
278
+ return {
279
+ 'raw_data': data,
280
+ 'formatting_error': str(e),
281
+ 'metadata': {
282
+ 'request_type': request_type,
283
+ 'units': units,
284
+ 'latitude': latitude,
285
+ 'longitude': longitude
286
+ }
287
+ }
288
+
289
+ async def _make_weather_request(self, url: str) -> Dict[str, Any]:
290
+ """
291
+ Make HTTP request to OpenWeather API using aiohttp.
292
+
293
+ Args:
294
+ url: Complete API URL
295
+
296
+ Returns:
297
+ API response as dictionary
298
+
299
+ Raises:
300
+ Exception: If request fails or returns invalid response
301
+ """
302
+ try:
303
+ timeout = aiohttp.ClientTimeout(total=self.timeout)
304
+
305
+ async with aiohttp.ClientSession(timeout=timeout) as session:
306
+ self.logger.info(f"Making weather API request: {url.split('&appid=')[0]}...")
307
+
308
+ async with session.get(url) as response:
309
+ if response.status == 200:
310
+ data = await response.json()
311
+ self.logger.info(f"Weather API request successful (status: {response.status})")
312
+ return data
313
+ else:
314
+ error_text = await response.text()
315
+ self.logger.error(f"Weather API request failed: {response.status} - {error_text}")
316
+ raise Exception(f"API request failed with status {response.status}: {error_text}")
317
+
318
+ except asyncio.TimeoutError:
319
+ raise Exception(f"Weather API request timed out after {self.timeout} seconds")
320
+ except aiohttp.ClientError as e:
321
+ raise Exception(f"HTTP client error: {str(e)}")
322
+ except Exception as e:
323
+ raise Exception(f"Weather API request failed: {str(e)}")
324
+
325
+ async def _execute(
326
+ self,
327
+ latitude: float,
328
+ longitude: float,
329
+ request_type: str = 'weather',
330
+ units: str = 'imperial',
331
+ country: str = 'us',
332
+ forecast_days: int = 3,
333
+ **kwargs
334
+ ) -> Dict[str, Any]:
335
+ """
336
+ Execute the weather request (AbstractTool interface).
337
+
338
+ Args:
339
+ latitude: Latitude coordinate
340
+ longitude: Longitude coordinate
341
+ request_type: Type of request ('weather' or 'forecast')
342
+ units: Temperature units ('metric', 'imperial', 'standard')
343
+ country: Country code
344
+ forecast_days: Number of forecast days (for forecast requests)
345
+ **kwargs: Additional arguments
346
+
347
+ Returns:
348
+ Formatted weather data
349
+ """
350
+ try:
351
+ self.logger.info(
352
+ f"Getting {request_type} for coordinates ({latitude}, {longitude}) "
353
+ f"in {units} units for country {country}"
354
+ )
355
+
356
+ # Build API URL
357
+ url = self._build_url(latitude, longitude, request_type, units, forecast_days)
358
+
359
+ # Make API request
360
+ raw_data = await self._make_weather_request(url)
361
+
362
+ # Format response
363
+ formatted_data = self._format_weather_response(
364
+ raw_data, request_type, units, latitude, longitude
365
+ )
366
+
367
+ self.logger.info(f"Weather data retrieved successfully for {request_type} request")
368
+ return formatted_data
369
+
370
+ except Exception as e:
371
+ self.logger.error(f"Error getting weather data: {e}")
372
+ raise
373
+
374
+ def execute_sync(
375
+ self,
376
+ latitude: float,
377
+ longitude: float,
378
+ request_type: str = 'weather',
379
+ units: str = 'imperial',
380
+ country: str = 'us',
381
+ forecast_days: int = 3
382
+ ) -> Dict[str, Any]:
383
+ """
384
+ Execute weather request synchronously.
385
+
386
+ Args:
387
+ latitude: Latitude coordinate
388
+ longitude: Longitude coordinate
389
+ request_type: Type of request ('weather' or 'forecast')
390
+ units: Temperature units
391
+ country: Country code
392
+ forecast_days: Number of forecast days
393
+
394
+ Returns:
395
+ Formatted weather data
396
+ """
397
+ try:
398
+ loop = asyncio.get_running_loop()
399
+ task = loop.create_task(self.execute(
400
+ latitude=latitude,
401
+ longitude=longitude,
402
+ request_type=request_type,
403
+ units=units,
404
+ country=country,
405
+ forecast_days=forecast_days
406
+ ))
407
+ return task
408
+ except RuntimeError:
409
+ return asyncio.run(self.execute(
410
+ latitude=latitude,
411
+ longitude=longitude,
412
+ request_type=request_type,
413
+ units=units,
414
+ country=country,
415
+ forecast_days=forecast_days
416
+ ))
417
+
418
+ def get_weather_summary(self, weather_data: ToolResult) -> str:
419
+ """
420
+ Generate a human-readable weather summary.
421
+
422
+ Args:
423
+ weather_result: ToolResult object containing weather data
424
+
425
+ Returns:
426
+ Human-readable weather summary
427
+ """
428
+ try:
429
+ # Extract the actual weather data from ToolResult
430
+ if weather_data.status != "success":
431
+ return f"Weather request failed: {weather_data.error or 'Unknown error'}"
432
+
433
+ weather_data = weather_data.result
434
+
435
+ if weather_data.get('current_weather'):
436
+ # Current weather summary
437
+ current = weather_data['current_weather']
438
+ location = weather_data['location']
439
+
440
+ summary = (
441
+ f"Current weather in {location['city']}, {location['country']}: "
442
+ f"{current['description']} with a temperature of {current['temperature']} "
443
+ f"(feels like {current['feels_like']}). "
444
+ f"Humidity: {current['humidity']}, "
445
+ f"Wind: {weather_data['wind']['speed']}"
446
+ )
447
+
448
+ elif weather_data.get('forecast'):
449
+ # Forecast summary
450
+ location = weather_data['location']
451
+ forecast_count = weather_data['forecast_summary']['total_entries']
452
+ period = weather_data['forecast_summary']['forecast_period']
453
+
454
+ summary = (
455
+ f"Weather forecast for {location['city']}, {location['country']}: "
456
+ f"{forecast_count} forecast entries from {period}"
457
+ )
458
+
459
+ else:
460
+ summary = "Weather data available (see full response for details)"
461
+
462
+ return summary
463
+
464
+ except Exception as e:
465
+ return f"Weather data retrieved (summary generation failed: {e})"
466
+
467
+ def save_weather_data(
468
+ self,
469
+ weather_result: ToolResult,
470
+ filename: Optional[str] = None
471
+ ) -> Dict[str, Any]:
472
+ """
473
+ Save weather data to JSON file.
474
+
475
+ Args:
476
+ weather_result: ToolResult object containing weather data
477
+ filename: Optional filename
478
+
479
+ Returns:
480
+ File information dictionary
481
+ """
482
+ if not self.output_dir:
483
+ raise ValueError("Output directory not configured")
484
+
485
+ # Extract weather data from ToolResult
486
+ if weather_result.status != "success":
487
+ raise ValueError(f"Cannot save failed weather request: {weather_result.error}")
488
+
489
+ weather_data = weather_result.result
490
+
491
+ if not filename:
492
+ timestamp = self.generate_filename("weather_data", "", include_timestamp=True)
493
+ filename = f"{timestamp}.json"
494
+ elif not filename.endswith('.json'):
495
+ filename = f"{filename}.json"
496
+
497
+ # Ensure output directory exists
498
+ self.output_dir.mkdir(parents=True, exist_ok=True)
499
+
500
+ file_path = self.output_dir / filename
501
+ file_path = self.validate_output_path(file_path)
502
+
503
+ try:
504
+ # Save both the result and metadata from ToolResult
505
+ save_data = {
506
+ "weather_data": weather_data,
507
+ "tool_metadata": {
508
+ "status": weather_result.status,
509
+ "timestamp": weather_result.timestamp,
510
+ "metadata": weather_result.metadata
511
+ }
512
+ }
513
+
514
+ with open(file_path, 'w', encoding='utf-8') as f:
515
+ json.dump(save_data, f, indent=2, default=str)
516
+
517
+ file_url = self.to_static_url(file_path)
518
+
519
+ return {
520
+ "filename": filename,
521
+ "file_path": str(file_path),
522
+ "file_url": file_url,
523
+ "file_size": file_path.stat().st_size
524
+ }
525
+
526
+ except Exception as e:
527
+ raise ValueError(f"Error saving weather data: {e}")