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
parrot/a2a/server.py ADDED
@@ -0,0 +1,770 @@
1
+ # parrot/a2a/server.py
2
+ """
3
+ A2A Server - Wraps an AI-Parrot Agent as an A2A-compliant HTTP service.
4
+ """
5
+ from __future__ import annotations
6
+ from typing import Dict, List, Optional, Any, TYPE_CHECKING, Union
7
+ import uuid
8
+ import json
9
+ import contextlib
10
+ import asyncio
11
+ from aiohttp import web
12
+ from navconfig.logging import logging
13
+ from .models import (
14
+ AgentCard,
15
+ AgentSkill,
16
+ AgentCapabilities,
17
+ Task,
18
+ TaskState,
19
+ TaskStatus,
20
+ Message,
21
+ Part,
22
+ Artifact,
23
+ Role,
24
+ )
25
+
26
+ if TYPE_CHECKING:
27
+ from ..bots.abstract import AbstractBot
28
+ from ..tools.abstract import AbstractTool
29
+
30
+
31
+ class A2AServer:
32
+ """
33
+ Wraps an AI-Parrot Agent (BasicAgent/AbstractBot) as an A2A HTTP service.
34
+
35
+ This server exposes your existing agent via the A2A protocol, automatically
36
+ generating the AgentCard from the agent's properties and tools.
37
+
38
+ Example:
39
+ from parrot.bots import Agent
40
+ from parrot.a2a import A2AServer
41
+
42
+ # Create your agent as usual
43
+ agent = Agent(
44
+ name="CustomerSupport",
45
+ llm="anthropic:claude-sonnet-4-20250514",
46
+ tools=[QueryCustomersTool(), CreateTicketTool()]
47
+ )
48
+ await agent.configure()
49
+
50
+ # Wrap it as A2A service
51
+ a2a = A2AServer(agent)
52
+
53
+ # Mount on your aiohttp app
54
+ app = web.Application()
55
+ a2a.setup(app)
56
+
57
+ # Agent is now accessible at:
58
+ # - GET /.well-known/agent.json (discovery)
59
+ # - POST /a2a/message/send (send message)
60
+ # - POST /a2a/message/stream (streaming)
61
+ # - GET /a2a/tasks/{id} (get task)
62
+ # etc.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ agent: "AbstractBot",
68
+ *,
69
+ base_path: str = "/a2a",
70
+ version: str = "1.0.0",
71
+ capabilities: Optional[AgentCapabilities] = None,
72
+ extra_skills: Optional[List[AgentSkill]] = None,
73
+ tags: Optional[List[str]] = None,
74
+ ):
75
+ """
76
+ Initialize A2A server wrapper.
77
+
78
+ Args:
79
+ agent: The AI-Parrot agent to expose (BasicAgent, etc.)
80
+ base_path: URL prefix for A2A endpoints (default: /a2a)
81
+ version: Version string for the AgentCard
82
+ capabilities: Override auto-detected capabilities
83
+ extra_skills: Additional skills beyond auto-discovered tools
84
+ tags: Tags for the AgentCard
85
+ """
86
+ self.agent = agent
87
+ self.base_path = base_path.rstrip("/")
88
+ self.version = version
89
+ self.capabilities = capabilities or AgentCapabilities(streaming=True)
90
+ self.extra_skills = extra_skills or []
91
+ self.tags = tags or []
92
+
93
+ # Runtime state
94
+ self._tasks: Dict[str, Task] = {}
95
+ self._app: Optional[web.Application] = None
96
+ self._url: Optional[str] = None
97
+ self._agent_card: Optional[AgentCard] = None
98
+
99
+ self.logger = logging.getLogger(f"A2A.{agent.name}")
100
+
101
+ def setup(self, app: web.Application, url: Optional[str] = None) -> None:
102
+ """
103
+ Register A2A routes on an aiohttp application.
104
+
105
+ Args:
106
+ app: The aiohttp Application
107
+ url: Public URL where this agent is accessible (for AgentCard)
108
+ """
109
+ self._app = app
110
+ self._url = url
111
+
112
+ # Store reference in app
113
+ app[f"a2a_server_{self.agent.name}"] = self
114
+
115
+ # Well-known agent card endpoint
116
+ app.router.add_get("/.well-known/agent.json", self._handle_agent_card)
117
+
118
+ # A2A HTTP+JSON Binding endpoints
119
+ app.router.add_post(f"{self.base_path}/message/send", self._handle_send_message)
120
+ app.router.add_post(f"{self.base_path}/message/stream", self._handle_stream_message)
121
+ app.router.add_get(f"{self.base_path}/tasks/{{task_id}}", self._handle_get_task)
122
+ app.router.add_get(f"{self.base_path}/tasks", self._handle_list_tasks)
123
+ app.router.add_post(f"{self.base_path}/tasks/{{task_id}}/cancel", self._handle_cancel_task)
124
+ app.router.add_get(f"{self.base_path}/tasks/{{task_id}}/subscribe", self._handle_subscribe)
125
+
126
+ # JSON-RPC binding (alternative)
127
+ app.router.add_post(f"{self.base_path}/rpc", self._handle_jsonrpc)
128
+
129
+ self.logger.info(
130
+ f"A2A server mounted for agent '{self.agent.name}' at {self.base_path}"
131
+ )
132
+
133
+ # ─────────────────────────────────────────────────────────────
134
+ # AgentCard Generation (from Agent properties)
135
+ # ─────────────────────────────────────────────────────────────
136
+
137
+ def get_agent_card(self) -> AgentCard:
138
+ """Generate AgentCard from the wrapped agent's properties."""
139
+ if self._agent_card:
140
+ return self._agent_card
141
+
142
+ # Build skills from agent's tools
143
+ skills = self._build_skills_from_tools()
144
+ skills.extend(self.extra_skills)
145
+
146
+ # Add a default "chat" skill if no tools
147
+ if not skills:
148
+ skills.append(AgentSkill(
149
+ id="chat",
150
+ name="Chat",
151
+ description=f"Have a conversation with {self.agent.name}",
152
+ tags=["conversation", "chat"],
153
+ ))
154
+
155
+ # Build description from agent properties
156
+ description_parts = []
157
+ if hasattr(self.agent, 'description') and self.agent.description:
158
+ description_parts.append(self.agent.description)
159
+ if hasattr(self.agent, 'role') and self.agent.role:
160
+ description_parts.append(f"Role: {self.agent.role}")
161
+ if hasattr(self.agent, 'goal') and self.agent.goal:
162
+ description_parts.append(f"Goal: {self.agent.goal}")
163
+
164
+ description = " | ".join(description_parts) if description_parts else f"AI Agent: {self.agent.name}"
165
+
166
+ self._agent_card = AgentCard(
167
+ name=self.agent.name,
168
+ description=description,
169
+ version=self.version,
170
+ url=self._url,
171
+ skills=skills,
172
+ capabilities=self.capabilities,
173
+ tags=self.tags or getattr(self.agent, 'tags', []),
174
+ )
175
+
176
+ return self._agent_card
177
+
178
+ def _build_skills_from_tools(self) -> List[AgentSkill]:
179
+ """Convert agent's tools to A2A skills."""
180
+ skills = []
181
+
182
+ # Get tools from tool_manager if available
183
+ if hasattr(self.agent, 'tool_manager'):
184
+ tools = self.agent.tool_manager.list_tools()
185
+ for tool_name in tools:
186
+ if tool := self.agent.tool_manager.get_tool(tool_name):
187
+ if skill := self._tool_to_skill(tool):
188
+ skills.append(skill)
189
+
190
+ # Also check direct tools attribute
191
+ elif hasattr(self.agent, 'tools') and self.agent.tools:
192
+ for tool in self.agent.tools:
193
+ if skill := self._tool_to_skill(tool):
194
+ skills.append(skill)
195
+
196
+ return skills
197
+
198
+ def _tool_to_skill(self, tool: "AbstractTool") -> Optional[AgentSkill]:
199
+ """Convert an AbstractTool to an AgentSkill."""
200
+ try:
201
+ name = getattr(tool, 'name', None)
202
+ if not name:
203
+ return None
204
+
205
+ description = getattr(tool, 'description', f"Tool: {name}")
206
+
207
+ # Try to get input schema from args_schema (Pydantic model)
208
+ input_schema = None
209
+ if hasattr(tool, 'args_schema') and tool.args_schema:
210
+ with contextlib.suppress(Exception):
211
+ input_schema = tool.args_schema.model_json_schema()
212
+
213
+ # Get tags if available
214
+ tags = getattr(tool, 'tags', [])
215
+ if isinstance(tags, str):
216
+ tags = [tags]
217
+
218
+ return AgentSkill(
219
+ id=name,
220
+ name=name.replace("_", " ").title(),
221
+ description=description,
222
+ tags=list(tags),
223
+ input_schema=input_schema,
224
+ )
225
+ except Exception as e:
226
+ self.logger.warning(f"Could not convert tool to skill: {e}")
227
+ return None
228
+
229
+ # ─────────────────────────────────────────────────────────────
230
+ # Core Message Processing (delegates to Agent)
231
+ # ─────────────────────────────────────────────────────────────
232
+
233
+ async def process_message(self, message: Message) -> Task:
234
+ """
235
+ Process an A2A message by delegating to the wrapped agent.
236
+ """
237
+ task = Task.create(context_id=message.context_id)
238
+ task.history.append(message)
239
+ self._tasks[task.id] = task
240
+
241
+ try:
242
+ task.working(f"Processing with {self.agent.name}...")
243
+
244
+ # Extract the question/input from message
245
+ question = message.get_text()
246
+ data = message.get_data()
247
+
248
+ # If structured data with skill/tool request
249
+ if data and "skill" in data:
250
+ response = await self._invoke_skill(data["skill"], data.get("params", {}))
251
+ elif data and "tool" in data:
252
+ response = await self._invoke_tool(data["tool"], data.get("params", {}))
253
+ else:
254
+ # Default: use agent's ask/chat method
255
+ response = await self._ask_agent(question, message)
256
+
257
+ task.complete(response)
258
+
259
+ except Exception as e:
260
+ self.logger.error(f"Error processing message: {e}", exc_info=True)
261
+ task.fail(str(e))
262
+
263
+ return task
264
+
265
+ async def _ask_agent(self, question: str, message: Message) -> Any:
266
+ """Delegate question to agent's ask/chat method."""
267
+ # Prepare kwargs for the agent
268
+ kwargs = self._build_ask_kwargs(message)
269
+
270
+ # Pass context_id as session_id if available
271
+ if message.context_id:
272
+ kwargs["session_id"] = message.context_id
273
+
274
+ # Use ask() method (most compatible)
275
+ if hasattr(self.agent, 'ask'):
276
+ response = await self.agent.ask(question, **kwargs)
277
+ elif hasattr(self.agent, 'chat'):
278
+ response = await self.agent.chat(question, **kwargs)
279
+ elif hasattr(self.agent, 'conversation'):
280
+ response = await self.agent.conversation(question, **kwargs)
281
+ else:
282
+ raise NotImplementedError(
283
+ f"Agent {self.agent.name} doesn't have ask/chat/conversation method"
284
+ )
285
+
286
+ return response
287
+
288
+ async def _invoke_skill(self, skill_id: str, params: Dict[str, Any]) -> Any:
289
+ """Invoke a specific skill (tool) by ID."""
290
+ return await self._invoke_tool(skill_id, params)
291
+
292
+ async def _invoke_tool(self, tool_name: str, params: Dict[str, Any]) -> Any:
293
+ """Invoke a specific tool by name."""
294
+ tool = None
295
+
296
+ # Try tool_manager first
297
+ if hasattr(self.agent, 'tool_manager'):
298
+ tool = self.agent.tool_manager.get_tool(tool_name)
299
+
300
+ # Try direct tools list
301
+ if not tool and hasattr(self.agent, 'tools'):
302
+ for t in self.agent.tools:
303
+ if getattr(t, 'name', None) == tool_name:
304
+ tool = t
305
+ break
306
+
307
+ if not tool:
308
+ raise ValueError(f"Tool/skill '{tool_name}' not found")
309
+
310
+ # Execute the tool
311
+ if hasattr(tool, '_execute'):
312
+ result = await tool._execute(**params)
313
+ elif hasattr(tool, 'run'):
314
+ result = await tool.run(**params)
315
+ elif hasattr(tool, '_arun'):
316
+ result = await tool._arun(**params)
317
+ else:
318
+ raise NotImplementedError(f"Tool {tool_name} has no executable method")
319
+
320
+ return result
321
+
322
+ # ─────────────────────────────────────────────────────────────
323
+ # HTTP Handlers
324
+ # ─────────────────────────────────────────────────────────────
325
+
326
+ async def _handle_agent_card(self, request: web.Request) -> web.Response:
327
+ """GET /.well-known/agent.json"""
328
+ card = self.get_agent_card()
329
+ return web.json_response(card.to_dict())
330
+
331
+ async def _handle_send_message(self, request: web.Request) -> web.Response:
332
+ """POST /a2a/message/send"""
333
+ try:
334
+ data = await request.json()
335
+ message = Message.from_dict(data.get("message", {}))
336
+ config = data.get("configuration", {})
337
+
338
+ task = await self.process_message(message)
339
+
340
+ # If blocking mode, wait for completion (already done in process_message)
341
+ # but if we had async processing, we'd wait here
342
+
343
+ return web.json_response(task.to_dict())
344
+
345
+ except json.JSONDecodeError:
346
+ return web.json_response(
347
+ {"error": {"code": "InvalidJSON", "message": "Invalid JSON body"}},
348
+ status=400
349
+ )
350
+ except Exception as e:
351
+ self.logger.error(f"Error in send_message: {e}", exc_info=True)
352
+ return web.json_response(
353
+ {"error": {"code": "InternalError", "message": str(e)}},
354
+ status=500
355
+ )
356
+
357
+ async def _handle_stream_message(self, request: web.Request) -> web.StreamResponse:
358
+ """POST /a2a/message/stream - SSE streaming response."""
359
+ response = web.StreamResponse(
360
+ headers={
361
+ "Content-Type": "text/event-stream",
362
+ "Cache-Control": "no-cache",
363
+ "Connection": "keep-alive",
364
+ }
365
+ )
366
+ await response.prepare(request)
367
+
368
+ try:
369
+ data = await request.json()
370
+ message = Message.from_dict(data.get("message", {}))
371
+
372
+ # Create task
373
+ task = Task.create(context_id=message.context_id)
374
+ task.history.append(message)
375
+ self._tasks[task.id] = task
376
+
377
+ # Send initial task
378
+ await self._send_sse(response, {"task": task.to_dict()})
379
+
380
+ # Send working status
381
+ task.working(f"Processing with {self.agent.name}...")
382
+ await self._send_sse(response, {
383
+ "statusUpdate": {
384
+ "taskId": task.id,
385
+ "contextId": task.context_id,
386
+ "status": {"state": "working"},
387
+ "final": False
388
+ }
389
+ })
390
+
391
+ # Process with streaming
392
+ try:
393
+ question = message.get_text()
394
+
395
+ # Try to use streaming method
396
+ if hasattr(self.agent, 'ask_stream'):
397
+ await self._stream_with_ask_stream(response, task, question, message)
398
+ else:
399
+ # Fallback to non-streaming
400
+ await self._stream_fallback(response, task, question, message)
401
+
402
+ except Exception as e:
403
+ self.logger.error(f"Error in streaming: {e}", exc_info=True)
404
+ task.fail(str(e))
405
+ await self._send_sse(response, {
406
+ "statusUpdate": {
407
+ "taskId": task.id,
408
+ "contextId": task.context_id,
409
+ "status": {
410
+ "state": "failed",
411
+ "message": {"role": "agent", "parts": [{"text": str(e)}]}
412
+ },
413
+ "final": True
414
+ }
415
+ })
416
+
417
+ except Exception as e:
418
+ self.logger.error(f"Error setting up stream: {e}", exc_info=True)
419
+ await self._send_sse(response, {"error": {"message": str(e)}})
420
+
421
+ await response.write_eof()
422
+ return response
423
+
424
+ async def _stream_with_ask_stream(
425
+ self,
426
+ response: web.StreamResponse,
427
+ task: Task,
428
+ question: str,
429
+ message: Message
430
+ ) -> None:
431
+ """Stream using agent's ask_stream method with light buffering."""
432
+ kwargs = {}
433
+ if message.context_id:
434
+ kwargs["session_id"] = message.context_id
435
+
436
+ collected_text = []
437
+ artifact_id = str(uuid.uuid4())
438
+
439
+ # Light buffering - balance between responsiveness and efficiency
440
+ buffer = []
441
+ buffer_size = 0
442
+ MIN_CHUNK_SIZE = 15 # ~3-4 words
443
+ MAX_BUFFER_TIME = 0.1 # 100ms max wait
444
+ last_flush = asyncio.get_event_loop().time()
445
+
446
+ async def flush_buffer():
447
+ nonlocal buffer, buffer_size, last_flush
448
+ if buffer:
449
+ chunk_text = "".join(buffer)
450
+ collected_text.append(chunk_text)
451
+
452
+ await self._send_sse(response, {
453
+ "artifactUpdate": {
454
+ "taskId": task.id,
455
+ "contextId": task.context_id,
456
+ "artifact": {
457
+ "artifactId": artifact_id,
458
+ "name": "response",
459
+ "parts": [{"text": chunk_text}]
460
+ },
461
+ "append": len(collected_text) > 1,
462
+ "lastChunk": False
463
+ }
464
+ })
465
+
466
+ buffer = []
467
+ buffer_size = 0
468
+ last_flush = asyncio.get_event_loop().time()
469
+
470
+ try:
471
+ async for chunk in self.agent.ask_stream(question, **kwargs):
472
+ chunk_text = self._extract_chunk_text(chunk)
473
+
474
+ if chunk_text:
475
+ buffer.append(chunk_text)
476
+ buffer_size += len(chunk_text)
477
+
478
+ current_time = asyncio.get_event_loop().time()
479
+ time_since_flush = current_time - last_flush
480
+
481
+ # Flush on size OR time threshold
482
+ if buffer_size >= MIN_CHUNK_SIZE or time_since_flush >= MAX_BUFFER_TIME:
483
+ await flush_buffer()
484
+
485
+ # Flush remaining
486
+ await flush_buffer()
487
+
488
+ # Final artifact with complete text
489
+ full_text = "".join(collected_text)
490
+ artifact = Artifact(
491
+ artifact_id=artifact_id,
492
+ name="response",
493
+ parts=[Part.from_text(full_text)]
494
+ )
495
+ task.artifacts.append(artifact)
496
+
497
+ await self._send_sse(response, {
498
+ "artifactUpdate": {
499
+ "taskId": task.id,
500
+ "contextId": task.context_id,
501
+ "artifact": artifact.to_dict(),
502
+ "append": False,
503
+ "lastChunk": True
504
+ }
505
+ })
506
+
507
+ task.status = TaskStatus(state=TaskState.COMPLETED)
508
+ await self._send_sse(response, {
509
+ "statusUpdate": {
510
+ "taskId": task.id,
511
+ "contextId": task.context_id,
512
+ "status": {"state": "completed"},
513
+ "final": True
514
+ }
515
+ })
516
+
517
+ except Exception as e:
518
+ self.logger.error(f"Streaming error: {e}", exc_info=True)
519
+ raise
520
+
521
+ async def _stream_fallback(
522
+ self,
523
+ response: web.StreamResponse,
524
+ task: Task,
525
+ question: str,
526
+ message: Message
527
+ ) -> None:
528
+ """Fallback when streaming is not available - use regular ask."""
529
+ result = await self._ask_agent(question, message)
530
+
531
+ # Send artifact
532
+ artifact = Artifact.from_response(result)
533
+ task.artifacts.append(artifact)
534
+ await self._send_sse(response, {
535
+ "artifactUpdate": {
536
+ "taskId": task.id,
537
+ "contextId": task.context_id,
538
+ "artifact": artifact.to_dict(),
539
+ "lastChunk": True
540
+ }
541
+ })
542
+
543
+ # Send completed
544
+ task.status = TaskStatus(state=TaskState.COMPLETED)
545
+ await self._send_sse(response, {
546
+ "statusUpdate": {
547
+ "taskId": task.id,
548
+ "contextId": task.context_id,
549
+ "status": {"state": "completed"},
550
+ "final": True
551
+ }
552
+ })
553
+
554
+ def _build_ask_kwargs(self, message: Message) -> Dict[str, Any]:
555
+ """Build kwargs for ask/ask_stream methods."""
556
+ kwargs = {}
557
+
558
+ # Get max_tokens from agent
559
+ max_tokens = getattr(self.agent, 'max_tokens', None)
560
+ if max_tokens is None and hasattr(self.agent, '_llm') and self.agent._llm:
561
+ max_tokens = getattr(self.agent._llm, 'max_tokens', None)
562
+ kwargs["max_tokens"] = max_tokens or 4096
563
+
564
+ # Pass context_id as session_id
565
+ if message.context_id:
566
+ kwargs["session_id"] = message.context_id
567
+
568
+ return kwargs
569
+
570
+ def _extract_chunk_text(self, chunk: Any) -> Optional[str]:
571
+ """Extract text content from a stream chunk."""
572
+ if chunk is None:
573
+ return None
574
+
575
+ # String chunk
576
+ if isinstance(chunk, str):
577
+ return chunk
578
+
579
+ # AIMessage or similar response object
580
+ if hasattr(chunk, 'content'):
581
+ return chunk.content
582
+ if hasattr(chunk, 'text'):
583
+ return chunk.text
584
+ if hasattr(chunk, 'delta'):
585
+ delta = chunk.delta
586
+ if hasattr(delta, 'text'):
587
+ return delta.text
588
+ if hasattr(delta, 'content'):
589
+ return delta.content
590
+
591
+ # Dict chunk
592
+ if isinstance(chunk, dict):
593
+ return chunk.get('text') or chunk.get('content') or chunk.get('delta', {}).get('text')
594
+
595
+ # Fallback
596
+ return str(chunk) if chunk else None
597
+
598
+ async def _send_sse(self, response: web.StreamResponse, data: Dict[str, Any]):
599
+ """Send SSE event."""
600
+ await response.write(f"data: {json.dumps(data)}\n\n".encode())
601
+
602
+ async def _handle_get_task(self, request: web.Request) -> web.Response:
603
+ """GET /a2a/tasks/{task_id}"""
604
+ task_id = request.match_info["task_id"]
605
+ if task := self._tasks.get(task_id):
606
+ return web.json_response(task.to_dict())
607
+
608
+ return web.json_response(
609
+ {"error": {"code": "TaskNotFoundError", "message": f"Task {task_id} not found"}},
610
+ status=404
611
+ )
612
+
613
+ async def _handle_list_tasks(self, request: web.Request) -> web.Response:
614
+ """GET /a2a/tasks"""
615
+ context_id = request.query.get("contextId")
616
+ state = request.query.get("status")
617
+ page_size = int(request.query.get("pageSize", 50))
618
+
619
+ tasks = list(self._tasks.values())
620
+
621
+ if context_id:
622
+ tasks = [t for t in tasks if t.context_id == context_id]
623
+ if state:
624
+ tasks = [t for t in tasks if t.status.state.value == state]
625
+
626
+ tasks = tasks[:page_size]
627
+
628
+ return web.json_response({
629
+ "tasks": [t.to_dict() for t in tasks],
630
+ "totalSize": len(tasks),
631
+ "pageSize": page_size,
632
+ "nextPageToken": ""
633
+ })
634
+
635
+ async def _handle_cancel_task(self, request: web.Request) -> web.Response:
636
+ """POST /a2a/tasks/{task_id}/cancel"""
637
+ task_id = request.match_info["task_id"]
638
+ task = self._tasks.get(task_id)
639
+
640
+ if not task:
641
+ return web.json_response(
642
+ {"error": {"code": "TaskNotFoundError"}},
643
+ status=404
644
+ )
645
+
646
+ terminal_states = {TaskState.COMPLETED, TaskState.FAILED, TaskState.CANCELLED}
647
+ if task.status.state in terminal_states:
648
+ return web.json_response(
649
+ {"error": {"code": "TaskNotCancelableError"}},
650
+ status=400
651
+ )
652
+
653
+ task.status = TaskStatus(state=TaskState.CANCELLED)
654
+ return web.json_response(task.to_dict())
655
+
656
+ async def _handle_subscribe(self, request: web.Request) -> web.StreamResponse:
657
+ """GET /a2a/tasks/{task_id}/subscribe"""
658
+ task_id = request.match_info["task_id"]
659
+ task = self._tasks.get(task_id)
660
+
661
+ if not task:
662
+ return web.json_response(
663
+ {"error": {"code": "TaskNotFoundError"}},
664
+ status=404
665
+ )
666
+
667
+ response = web.StreamResponse(
668
+ headers={"Content-Type": "text/event-stream"}
669
+ )
670
+ await response.prepare(request)
671
+
672
+ # Send current state
673
+ await self._send_sse(response, {"task": task.to_dict()})
674
+
675
+ # For now, just close (in production, would subscribe to updates)
676
+ await response.write_eof()
677
+ return response
678
+
679
+ async def _handle_jsonrpc(self, request: web.Request) -> web.Response:
680
+ """POST /a2a/rpc - JSON-RPC 2.0 binding."""
681
+ data = await request.json()
682
+ method = data.get("method")
683
+ params = data.get("params", {})
684
+ req_id = data.get("id")
685
+
686
+ try:
687
+ if method == "message/send":
688
+ message = Message.from_dict(params.get("message", {}))
689
+ task = await self.process_message(message)
690
+ result = task.to_dict()
691
+ elif method == "tasks/get":
692
+ task = self._tasks.get(params.get("id"))
693
+ result = task.to_dict() if task else None
694
+ elif method == "tasks/list":
695
+ tasks = list(self._tasks.values())
696
+ result = {"tasks": [t.to_dict() for t in tasks]}
697
+ else:
698
+ return web.json_response({
699
+ "jsonrpc": "2.0",
700
+ "id": req_id,
701
+ "error": {"code": -32601, "message": f"Method not found: {method}"}
702
+ })
703
+
704
+ return web.json_response({
705
+ "jsonrpc": "2.0",
706
+ "id": req_id,
707
+ "result": result
708
+ })
709
+ except Exception as e:
710
+ return web.json_response({
711
+ "jsonrpc": "2.0",
712
+ "id": req_id,
713
+ "error": {"code": -32603, "message": str(e)}
714
+ })
715
+
716
+
717
+ class A2AEnabledMixin:
718
+ """
719
+ Mixin to add A2A server capabilities to an agent class.
720
+
721
+ Similar to MCPEnabledMixin, this adds A2A methods directly to your agent.
722
+
723
+ Example:
724
+ class MyAgent(A2AEnabledMixin, BasicAgent):
725
+ pass
726
+
727
+ agent = MyAgent(name="test", llm="openai:gpt-4")
728
+ await agent.configure()
729
+
730
+ # Start A2A server
731
+ app = web.Application()
732
+ agent.setup_a2a(app, url="https://my-agent.example.com")
733
+ """
734
+
735
+ _a2a_server: Optional[A2AServer] = None
736
+
737
+ def setup_a2a(
738
+ self,
739
+ app: web.Application,
740
+ url: Optional[str] = None,
741
+ base_path: str = "/a2a",
742
+ **kwargs
743
+ ) -> A2AServer:
744
+ """
745
+ Setup A2A server for this agent.
746
+
747
+ Args:
748
+ app: aiohttp Application to mount routes on
749
+ url: Public URL for AgentCard
750
+ base_path: URL prefix for A2A endpoints
751
+ **kwargs: Additional A2AServer options
752
+
753
+ Returns:
754
+ The A2AServer instance
755
+ """
756
+ self._a2a_server = A2AServer(
757
+ self,
758
+ base_path=base_path,
759
+ **kwargs
760
+ )
761
+ self._a2a_server.setup(app, url=url)
762
+ return self._a2a_server
763
+
764
+ def get_a2a_server(self) -> Optional[A2AServer]:
765
+ """Get the A2A server instance if setup."""
766
+ return self._a2a_server
767
+
768
+ def get_agent_card(self) -> Optional[AgentCard]:
769
+ """Get the AgentCard if A2A is setup."""
770
+ return self._a2a_server.get_agent_card() if self._a2a_server else None