agno 2.0.0rc2__py3-none-any.whl → 2.3.0__py3-none-any.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 (331) hide show
  1. agno/agent/agent.py +6009 -2874
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +595 -187
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +3 -0
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +339 -266
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +1011 -566
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +110 -37
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +143 -4
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +60 -6
  142. agno/models/openai/chat.py +102 -43
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +81 -5
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -175
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +266 -112
  205. agno/run/base.py +53 -24
  206. agno/run/team.py +252 -111
  207. agno/run/workflow.py +156 -45
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1692
  213. agno/tools/brightdata.py +3 -3
  214. agno/tools/cartesia.py +3 -5
  215. agno/tools/dalle.py +9 -8
  216. agno/tools/decorator.py +4 -2
  217. agno/tools/desi_vocal.py +2 -2
  218. agno/tools/duckduckgo.py +15 -11
  219. agno/tools/e2b.py +20 -13
  220. agno/tools/eleven_labs.py +26 -28
  221. agno/tools/exa.py +21 -16
  222. agno/tools/fal.py +4 -4
  223. agno/tools/file.py +153 -23
  224. agno/tools/file_generation.py +350 -0
  225. agno/tools/firecrawl.py +4 -4
  226. agno/tools/function.py +257 -37
  227. agno/tools/giphy.py +2 -2
  228. agno/tools/gmail.py +238 -14
  229. agno/tools/google_drive.py +270 -0
  230. agno/tools/googlecalendar.py +36 -8
  231. agno/tools/googlesheets.py +20 -5
  232. agno/tools/jira.py +20 -0
  233. agno/tools/knowledge.py +3 -3
  234. agno/tools/lumalab.py +3 -3
  235. agno/tools/mcp/__init__.py +10 -0
  236. agno/tools/mcp/mcp.py +331 -0
  237. agno/tools/mcp/multi_mcp.py +347 -0
  238. agno/tools/mcp/params.py +24 -0
  239. agno/tools/mcp_toolbox.py +284 -0
  240. agno/tools/mem0.py +11 -17
  241. agno/tools/memori.py +1 -53
  242. agno/tools/memory.py +419 -0
  243. agno/tools/models/azure_openai.py +2 -2
  244. agno/tools/models/gemini.py +3 -3
  245. agno/tools/models/groq.py +3 -5
  246. agno/tools/models/nebius.py +7 -7
  247. agno/tools/models_labs.py +25 -15
  248. agno/tools/notion.py +204 -0
  249. agno/tools/openai.py +4 -9
  250. agno/tools/opencv.py +3 -3
  251. agno/tools/parallel.py +314 -0
  252. agno/tools/replicate.py +7 -7
  253. agno/tools/scrapegraph.py +58 -31
  254. agno/tools/searxng.py +2 -2
  255. agno/tools/serper.py +2 -2
  256. agno/tools/slack.py +18 -3
  257. agno/tools/spider.py +2 -2
  258. agno/tools/tavily.py +146 -0
  259. agno/tools/whatsapp.py +1 -1
  260. agno/tools/workflow.py +278 -0
  261. agno/tools/yfinance.py +12 -11
  262. agno/utils/agent.py +820 -0
  263. agno/utils/audio.py +27 -0
  264. agno/utils/common.py +90 -1
  265. agno/utils/events.py +222 -7
  266. agno/utils/gemini.py +181 -23
  267. agno/utils/hooks.py +57 -0
  268. agno/utils/http.py +111 -0
  269. agno/utils/knowledge.py +12 -5
  270. agno/utils/log.py +1 -0
  271. agno/utils/mcp.py +95 -5
  272. agno/utils/media.py +188 -10
  273. agno/utils/merge_dict.py +22 -1
  274. agno/utils/message.py +60 -0
  275. agno/utils/models/claude.py +40 -11
  276. agno/utils/models/cohere.py +1 -1
  277. agno/utils/models/watsonx.py +1 -1
  278. agno/utils/openai.py +1 -1
  279. agno/utils/print_response/agent.py +105 -21
  280. agno/utils/print_response/team.py +103 -38
  281. agno/utils/print_response/workflow.py +251 -34
  282. agno/utils/reasoning.py +22 -1
  283. agno/utils/serialize.py +32 -0
  284. agno/utils/streamlit.py +16 -10
  285. agno/utils/string.py +41 -0
  286. agno/utils/team.py +98 -9
  287. agno/utils/tools.py +1 -1
  288. agno/vectordb/base.py +23 -4
  289. agno/vectordb/cassandra/cassandra.py +65 -9
  290. agno/vectordb/chroma/chromadb.py +182 -38
  291. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  292. agno/vectordb/couchbase/couchbase.py +105 -10
  293. agno/vectordb/lancedb/lance_db.py +183 -135
  294. agno/vectordb/langchaindb/langchaindb.py +25 -7
  295. agno/vectordb/lightrag/lightrag.py +17 -3
  296. agno/vectordb/llamaindex/__init__.py +3 -0
  297. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  298. agno/vectordb/milvus/milvus.py +126 -9
  299. agno/vectordb/mongodb/__init__.py +7 -1
  300. agno/vectordb/mongodb/mongodb.py +112 -7
  301. agno/vectordb/pgvector/pgvector.py +142 -21
  302. agno/vectordb/pineconedb/pineconedb.py +80 -8
  303. agno/vectordb/qdrant/qdrant.py +125 -39
  304. agno/vectordb/redis/__init__.py +9 -0
  305. agno/vectordb/redis/redisdb.py +694 -0
  306. agno/vectordb/singlestore/singlestore.py +111 -25
  307. agno/vectordb/surrealdb/surrealdb.py +31 -5
  308. agno/vectordb/upstashdb/upstashdb.py +76 -8
  309. agno/vectordb/weaviate/weaviate.py +86 -15
  310. agno/workflow/__init__.py +2 -0
  311. agno/workflow/agent.py +299 -0
  312. agno/workflow/condition.py +112 -18
  313. agno/workflow/loop.py +69 -10
  314. agno/workflow/parallel.py +266 -118
  315. agno/workflow/router.py +110 -17
  316. agno/workflow/step.py +645 -136
  317. agno/workflow/steps.py +65 -6
  318. agno/workflow/types.py +71 -33
  319. agno/workflow/workflow.py +2113 -300
  320. agno-2.3.0.dist-info/METADATA +618 -0
  321. agno-2.3.0.dist-info/RECORD +577 -0
  322. agno-2.3.0.dist-info/licenses/LICENSE +201 -0
  323. agno/knowledge/reader/url_reader.py +0 -128
  324. agno/tools/googlesearch.py +0 -98
  325. agno/tools/mcp.py +0 -610
  326. agno/utils/models/aws_claude.py +0 -170
  327. agno-2.0.0rc2.dist-info/METADATA +0 -355
  328. agno-2.0.0rc2.dist-info/RECORD +0 -515
  329. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  330. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  331. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,924 @@
1
+ import json
2
+ from typing import Any, Dict, cast
3
+ from uuid import uuid4
4
+
5
+ from fastapi import HTTPException
6
+ from typing_extensions import AsyncIterator, List, Union
7
+
8
+ from agno.run.team import MemoryUpdateCompletedEvent as TeamMemoryUpdateCompletedEvent
9
+ from agno.run.team import MemoryUpdateStartedEvent as TeamMemoryUpdateStartedEvent
10
+ from agno.run.team import ReasoningCompletedEvent as TeamReasoningCompletedEvent
11
+ from agno.run.team import ReasoningStartedEvent as TeamReasoningStartedEvent
12
+ from agno.run.team import ReasoningStepEvent as TeamReasoningStepEvent
13
+ from agno.run.team import RunCancelledEvent as TeamRunCancelledEvent
14
+ from agno.run.team import RunCompletedEvent as TeamRunCompletedEvent
15
+ from agno.run.team import RunContentEvent as TeamRunContentEvent
16
+ from agno.run.team import RunStartedEvent as TeamRunStartedEvent
17
+ from agno.run.team import TeamRunOutputEvent
18
+ from agno.run.team import ToolCallCompletedEvent as TeamToolCallCompletedEvent
19
+ from agno.run.team import ToolCallStartedEvent as TeamToolCallStartedEvent
20
+ from agno.run.workflow import (
21
+ ConditionExecutionCompletedEvent,
22
+ ConditionExecutionStartedEvent,
23
+ LoopExecutionCompletedEvent,
24
+ LoopExecutionStartedEvent,
25
+ LoopIterationCompletedEvent,
26
+ LoopIterationStartedEvent,
27
+ ParallelExecutionCompletedEvent,
28
+ ParallelExecutionStartedEvent,
29
+ RouterExecutionCompletedEvent,
30
+ RouterExecutionStartedEvent,
31
+ StepsExecutionCompletedEvent,
32
+ StepsExecutionStartedEvent,
33
+ WorkflowCancelledEvent,
34
+ WorkflowCompletedEvent,
35
+ WorkflowRunOutput,
36
+ WorkflowRunOutputEvent,
37
+ WorkflowStartedEvent,
38
+ )
39
+ from agno.run.workflow import StepCompletedEvent as WorkflowStepCompletedEvent
40
+ from agno.run.workflow import StepErrorEvent as WorkflowStepErrorEvent
41
+ from agno.run.workflow import StepStartedEvent as WorkflowStepStartedEvent
42
+
43
+ try:
44
+ from a2a.types import (
45
+ Artifact,
46
+ DataPart,
47
+ FilePart,
48
+ FileWithBytes,
49
+ FileWithUri,
50
+ Part,
51
+ Role,
52
+ SendMessageRequest,
53
+ SendStreamingMessageRequest,
54
+ SendStreamingMessageSuccessResponse,
55
+ Task,
56
+ TaskState,
57
+ TaskStatus,
58
+ TaskStatusUpdateEvent,
59
+ TextPart,
60
+ )
61
+ from a2a.types import Message as A2AMessage
62
+ except ImportError as e:
63
+ raise ImportError("`a2a` not installed. Please install it with `pip install -U a2a`") from e
64
+
65
+
66
+ from agno.media import Audio, File, Image, Video
67
+ from agno.run.agent import (
68
+ MemoryUpdateCompletedEvent,
69
+ MemoryUpdateStartedEvent,
70
+ ReasoningCompletedEvent,
71
+ ReasoningStartedEvent,
72
+ ReasoningStepEvent,
73
+ RunCancelledEvent,
74
+ RunCompletedEvent,
75
+ RunContentEvent,
76
+ RunInput,
77
+ RunOutput,
78
+ RunOutputEvent,
79
+ RunStartedEvent,
80
+ ToolCallCompletedEvent,
81
+ ToolCallStartedEvent,
82
+ )
83
+
84
+
85
+ async def map_a2a_request_to_run_input(request_body: dict, stream: bool = True) -> RunInput:
86
+ """Map A2A SendMessageRequest to Agno RunInput.
87
+
88
+ 1. Validate the request
89
+ 2. Process message parts
90
+ 3. Build and return RunInput
91
+
92
+ Args:
93
+ request_body: A2A-valid JSON-RPC request body dict:
94
+
95
+ ```json
96
+ {
97
+ "jsonrpc": "2.0",
98
+ "method": "message/send",
99
+ "id": "id",
100
+ "params": {
101
+ "message": {
102
+ "messageId": "id",
103
+ "role": "user",
104
+ "contextId": "id",
105
+ "parts": [{"kind": "text", "text": "Hello"}]
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ Returns:
112
+ RunInput: The Agno RunInput
113
+ stream: Wheter we are in stream mode
114
+ """
115
+
116
+ # 1. Validate the request
117
+ if stream:
118
+ try:
119
+ a2a_request = SendStreamingMessageRequest.model_validate(request_body)
120
+ except Exception as e:
121
+ raise HTTPException(status_code=400, detail=f"Invalid A2A request: {str(e)}")
122
+ else:
123
+ try:
124
+ a2a_request = SendMessageRequest.model_validate(request_body) # type: ignore[assignment]
125
+ except Exception as e:
126
+ raise HTTPException(status_code=400, detail=f"Invalid A2A request: {str(e)}")
127
+
128
+ a2a_message = a2a_request.params.message
129
+ if a2a_message.role != "user":
130
+ raise HTTPException(status_code=400, detail="Only user messages are accepted")
131
+
132
+ # 2. Process message parts
133
+ text_parts = []
134
+ images = []
135
+ videos = []
136
+ audios = []
137
+ files = []
138
+
139
+ for part in a2a_message.parts:
140
+ # Handle message text content
141
+ if isinstance(part.root, TextPart):
142
+ text_parts.append(part.root.text)
143
+
144
+ # Handle message files
145
+ elif isinstance(part.root, FilePart):
146
+ file_data = part.root.file
147
+ if isinstance(file_data, FileWithUri):
148
+ if not file_data.mime_type:
149
+ continue
150
+ elif file_data.mime_type.startswith("image/"):
151
+ images.append(Image(url=file_data.uri))
152
+ elif file_data.mime_type.startswith("video/"):
153
+ videos.append(Video(url=file_data.uri))
154
+ elif file_data.mime_type.startswith("audio/"):
155
+ audios.append(Audio(url=file_data.uri))
156
+ else:
157
+ files.append(File(url=file_data.uri, mime_type=file_data.mime_type))
158
+ elif isinstance(file_data, FileWithBytes):
159
+ if not file_data.mime_type:
160
+ continue
161
+ files.append(File(content=file_data.bytes, mime_type=file_data.mime_type))
162
+
163
+ # Handle message structured data parts
164
+ elif isinstance(part.root, DataPart):
165
+ import json
166
+
167
+ text_parts.append(json.dumps(part.root.data))
168
+
169
+ # 3. Build and return RunInput
170
+ complete_input_content = "\n".join(text_parts) if text_parts else ""
171
+ return RunInput(
172
+ input_content=complete_input_content,
173
+ images=images if images else None,
174
+ videos=videos if videos else None,
175
+ audios=audios if audios else None,
176
+ files=files if files else None,
177
+ )
178
+
179
+
180
+ def map_run_output_to_a2a_task(run_output: Union[RunOutput, WorkflowRunOutput]) -> Task:
181
+ """Map the given RunOutput or WorkflowRunOutput into an A2A Task.
182
+
183
+ 1. Handle output content
184
+ 2. Handle output media
185
+ 3. Build the A2A message
186
+ 4. Build and return the A2A task
187
+
188
+ Args:
189
+ run_output: The Agno RunOutput or WorkflowRunOutput
190
+
191
+ Returns:
192
+ Task: The A2A Task
193
+ """
194
+ parts: List[Part] = []
195
+
196
+ # 1. Handle output content
197
+ if run_output.content:
198
+ parts.append(Part(root=TextPart(text=str(run_output.content))))
199
+
200
+ # 2. Handle output media
201
+ artifacts: List[Artifact] = []
202
+ if hasattr(run_output, "images") and run_output.images:
203
+ for idx, img in enumerate(run_output.images):
204
+ artifact_parts = []
205
+ if img.url:
206
+ artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=img.url, mime_type="image/jpeg"))))
207
+ artifacts.append(
208
+ Artifact(
209
+ artifact_id=str(uuid4()),
210
+ name=f"image_{idx}",
211
+ description=f"Generated image {idx}",
212
+ parts=artifact_parts,
213
+ )
214
+ )
215
+ if hasattr(run_output, "videos") and run_output.videos:
216
+ for idx, vid in enumerate(run_output.videos):
217
+ artifact_parts = []
218
+ if vid.url:
219
+ artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=vid.url, mime_type="video/mp4"))))
220
+ artifacts.append(
221
+ Artifact(
222
+ artifact_id=str(uuid4()),
223
+ name=f"video_{idx}",
224
+ description=f"Generated video {idx}",
225
+ parts=artifact_parts,
226
+ )
227
+ )
228
+ if hasattr(run_output, "audio") and run_output.audio:
229
+ for idx, aud in enumerate(run_output.audio):
230
+ artifact_parts = []
231
+ if aud.url:
232
+ artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=aud.url, mime_type="audio/mpeg"))))
233
+ artifacts.append(
234
+ Artifact(
235
+ artifact_id=str(uuid4()),
236
+ name=f"audio_{idx}",
237
+ description=f"Generated audio {idx}",
238
+ parts=artifact_parts,
239
+ )
240
+ )
241
+ if hasattr(run_output, "files") and run_output.files:
242
+ for idx, file in enumerate(run_output.files):
243
+ artifact_parts = []
244
+ if file.url:
245
+ artifact_parts.append(
246
+ Part(
247
+ root=FilePart(
248
+ file=FileWithUri(uri=file.url, mime_type=file.mime_type or "application/octet-stream")
249
+ )
250
+ )
251
+ )
252
+ artifacts.append(
253
+ Artifact(
254
+ artifact_id=str(uuid4()),
255
+ name=getattr(file, "name", f"file_{idx}"),
256
+ description=f"Generated file {idx}",
257
+ parts=artifact_parts,
258
+ )
259
+ )
260
+
261
+ # 3. Build the A2A message
262
+ metadata = {}
263
+ if hasattr(run_output, "user_id") and run_output.user_id:
264
+ metadata["userId"] = run_output.user_id
265
+
266
+ agent_message = A2AMessage(
267
+ message_id=str(uuid4()), # TODO: use our message_id once it's implemented
268
+ role=Role.agent,
269
+ parts=parts,
270
+ context_id=run_output.session_id,
271
+ task_id=run_output.run_id,
272
+ metadata=metadata if metadata else None,
273
+ )
274
+
275
+ # 4. Build and return the A2A task
276
+ run_id = cast(str, run_output.run_id) if run_output.run_id else str(uuid4())
277
+ session_id = cast(str, run_output.session_id) if run_output.session_id else str(uuid4())
278
+ return Task(
279
+ id=run_id,
280
+ context_id=session_id,
281
+ status=TaskStatus(state=TaskState.completed),
282
+ history=[agent_message],
283
+ artifacts=artifacts if artifacts else None,
284
+ )
285
+
286
+
287
+ async def stream_a2a_response(
288
+ event_stream: AsyncIterator[Union[RunOutputEvent, TeamRunOutputEvent, WorkflowRunOutputEvent, RunOutput]],
289
+ request_id: Union[str, int],
290
+ ) -> AsyncIterator[str]:
291
+ """Stream the given event stream as A2A responses.
292
+
293
+ 1. Send initial event
294
+ 2. Send content and secondary events
295
+ 3. Send final status event
296
+ 4. Send final complete task
297
+
298
+ Args:
299
+ event_stream: The async iterator of Agno events from agent/team/workflow.arun(stream=True)
300
+ request_id: The JSON-RPC request ID
301
+
302
+ Yields:
303
+ str: JSON-RPC response objects (A2A-valid)
304
+ """
305
+ task_id: str = str(uuid4())
306
+ context_id: str = str(uuid4())
307
+ message_id: str = str(uuid4())
308
+ accumulated_content = ""
309
+ completion_event = None
310
+ cancelled_event = None
311
+
312
+ # Stream events
313
+ async for event in event_stream:
314
+ # 1. Send initial event
315
+ if isinstance(event, (RunStartedEvent, TeamRunStartedEvent, WorkflowStartedEvent)):
316
+ if hasattr(event, "run_id") and event.run_id:
317
+ task_id = event.run_id
318
+ if hasattr(event, "session_id") and event.session_id:
319
+ context_id = event.session_id
320
+
321
+ status_event = TaskStatusUpdateEvent(
322
+ task_id=task_id,
323
+ context_id=context_id,
324
+ status=TaskStatus(state=TaskState.working),
325
+ final=False,
326
+ )
327
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
328
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
329
+
330
+ # 2. Send all content and secondary events
331
+
332
+ # Send content events
333
+ elif isinstance(event, (RunContentEvent, TeamRunContentEvent)) and event.content:
334
+ accumulated_content += event.content
335
+ message = A2AMessage(
336
+ message_id=message_id,
337
+ role=Role.agent,
338
+ parts=[Part(root=TextPart(text=event.content))],
339
+ context_id=context_id,
340
+ task_id=task_id,
341
+ metadata={"agno_content_category": "content"},
342
+ )
343
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=message)
344
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
345
+
346
+ # Send tool call events
347
+ elif isinstance(event, (ToolCallStartedEvent, TeamToolCallStartedEvent)):
348
+ metadata: Dict[str, Any] = {"agno_event_type": "tool_call_started"}
349
+ if event.tool:
350
+ metadata["tool_name"] = event.tool.tool_name or "tool"
351
+ if hasattr(event.tool, "tool_call_id") and event.tool.tool_call_id:
352
+ metadata["tool_call_id"] = event.tool.tool_call_id
353
+ if hasattr(event.tool, "tool_args") and event.tool.tool_args:
354
+ metadata["tool_args"] = json.dumps(event.tool.tool_args)
355
+
356
+ status_event = TaskStatusUpdateEvent(
357
+ task_id=task_id,
358
+ context_id=context_id,
359
+ status=TaskStatus(state=TaskState.working),
360
+ final=False,
361
+ metadata=metadata,
362
+ )
363
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
364
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
365
+
366
+ elif isinstance(event, (ToolCallCompletedEvent, TeamToolCallCompletedEvent)):
367
+ metadata = {"agno_event_type": "tool_call_completed"}
368
+ if event.tool:
369
+ metadata["tool_name"] = event.tool.tool_name or "tool"
370
+ if hasattr(event.tool, "tool_call_id") and event.tool.tool_call_id:
371
+ metadata["tool_call_id"] = event.tool.tool_call_id
372
+ if hasattr(event.tool, "tool_args") and event.tool.tool_args:
373
+ metadata["tool_args"] = json.dumps(event.tool.tool_args)
374
+
375
+ status_event = TaskStatusUpdateEvent(
376
+ task_id=task_id,
377
+ context_id=context_id,
378
+ status=TaskStatus(state=TaskState.working),
379
+ final=False,
380
+ metadata=metadata,
381
+ )
382
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
383
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
384
+
385
+ # Send reasoning events
386
+ elif isinstance(event, (ReasoningStartedEvent, TeamReasoningStartedEvent)):
387
+ status_event = TaskStatusUpdateEvent(
388
+ task_id=task_id,
389
+ context_id=context_id,
390
+ status=TaskStatus(state=TaskState.working),
391
+ final=False,
392
+ metadata={"agno_event_type": "reasoning_started"},
393
+ )
394
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
395
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
396
+
397
+ elif isinstance(event, (ReasoningStepEvent, TeamReasoningStepEvent)):
398
+ if event.reasoning_content:
399
+ # Send reasoning step as a message
400
+ reasoning_message = A2AMessage(
401
+ message_id=str(uuid4()),
402
+ role=Role.agent,
403
+ parts=[
404
+ Part(
405
+ root=TextPart(
406
+ text=event.reasoning_content,
407
+ metadata={
408
+ "step_type": event.content_type if event.content_type else "str",
409
+ },
410
+ )
411
+ )
412
+ ],
413
+ context_id=context_id,
414
+ task_id=task_id,
415
+ metadata={"agno_content_category": "reasoning", "agno_event_type": "reasoning_step"},
416
+ )
417
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=reasoning_message)
418
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
419
+
420
+ elif isinstance(event, (ReasoningCompletedEvent, TeamReasoningCompletedEvent)):
421
+ status_event = TaskStatusUpdateEvent(
422
+ task_id=task_id,
423
+ context_id=context_id,
424
+ status=TaskStatus(state=TaskState.working),
425
+ final=False,
426
+ metadata={"agno_event_type": "reasoning_completed"},
427
+ )
428
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
429
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
430
+
431
+ # Send memory update events
432
+ elif isinstance(event, (MemoryUpdateStartedEvent, TeamMemoryUpdateStartedEvent)):
433
+ status_event = TaskStatusUpdateEvent(
434
+ task_id=task_id,
435
+ context_id=context_id,
436
+ status=TaskStatus(state=TaskState.working),
437
+ final=False,
438
+ metadata={"agno_event_type": "memory_update_started"},
439
+ )
440
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
441
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
442
+
443
+ elif isinstance(event, (MemoryUpdateCompletedEvent, TeamMemoryUpdateCompletedEvent)):
444
+ status_event = TaskStatusUpdateEvent(
445
+ task_id=task_id,
446
+ context_id=context_id,
447
+ status=TaskStatus(state=TaskState.working),
448
+ final=False,
449
+ metadata={"agno_event_type": "memory_update_completed"},
450
+ )
451
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
452
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
453
+
454
+ # Send workflow events
455
+ elif isinstance(event, WorkflowStepStartedEvent):
456
+ metadata = {"agno_event_type": "workflow_step_started"}
457
+ if hasattr(event, "step_name") and event.step_name:
458
+ metadata["step_name"] = event.step_name
459
+
460
+ status_event = TaskStatusUpdateEvent(
461
+ task_id=task_id,
462
+ context_id=context_id,
463
+ status=TaskStatus(state=TaskState.working),
464
+ final=False,
465
+ metadata=metadata,
466
+ )
467
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
468
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
469
+
470
+ elif isinstance(event, WorkflowStepCompletedEvent):
471
+ metadata = {"agno_event_type": "workflow_step_completed"}
472
+ if hasattr(event, "step_name") and event.step_name:
473
+ metadata["step_name"] = event.step_name
474
+
475
+ status_event = TaskStatusUpdateEvent(
476
+ task_id=task_id,
477
+ context_id=context_id,
478
+ status=TaskStatus(state=TaskState.working),
479
+ final=False,
480
+ metadata=metadata,
481
+ )
482
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
483
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
484
+
485
+ elif isinstance(event, WorkflowStepErrorEvent):
486
+ metadata = {"agno_event_type": "workflow_step_error"}
487
+ if hasattr(event, "step_name") and event.step_name:
488
+ metadata["step_name"] = event.step_name
489
+ if hasattr(event, "error") and event.error:
490
+ metadata["error"] = event.error
491
+
492
+ status_event = TaskStatusUpdateEvent(
493
+ task_id=task_id,
494
+ context_id=context_id,
495
+ status=TaskStatus(state=TaskState.working),
496
+ final=False,
497
+ metadata=metadata,
498
+ )
499
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
500
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
501
+
502
+ # Send loop events
503
+ elif isinstance(event, LoopExecutionStartedEvent):
504
+ metadata = {"agno_event_type": "loop_execution_started"}
505
+ if hasattr(event, "step_name") and event.step_name:
506
+ metadata["step_name"] = event.step_name
507
+ if hasattr(event, "max_iterations") and event.max_iterations:
508
+ metadata["max_iterations"] = event.max_iterations
509
+
510
+ status_event = TaskStatusUpdateEvent(
511
+ task_id=task_id,
512
+ context_id=context_id,
513
+ status=TaskStatus(state=TaskState.working),
514
+ final=False,
515
+ metadata=metadata,
516
+ )
517
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
518
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
519
+
520
+ elif isinstance(event, LoopIterationStartedEvent):
521
+ metadata = {"agno_event_type": "loop_iteration_started"}
522
+ if hasattr(event, "step_name") and event.step_name:
523
+ metadata["step_name"] = event.step_name
524
+ if hasattr(event, "iteration") and event.iteration is not None:
525
+ metadata["iteration"] = event.iteration
526
+ if hasattr(event, "max_iterations") and event.max_iterations:
527
+ metadata["max_iterations"] = event.max_iterations
528
+
529
+ status_event = TaskStatusUpdateEvent(
530
+ task_id=task_id,
531
+ context_id=context_id,
532
+ status=TaskStatus(state=TaskState.working),
533
+ final=False,
534
+ metadata=metadata,
535
+ )
536
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
537
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
538
+
539
+ elif isinstance(event, LoopIterationCompletedEvent):
540
+ metadata = {"agno_event_type": "loop_iteration_completed"}
541
+ if hasattr(event, "step_name") and event.step_name:
542
+ metadata["step_name"] = event.step_name
543
+ if hasattr(event, "iteration") and event.iteration is not None:
544
+ metadata["iteration"] = event.iteration
545
+ if hasattr(event, "should_continue") and event.should_continue is not None:
546
+ metadata["should_continue"] = event.should_continue
547
+
548
+ status_event = TaskStatusUpdateEvent(
549
+ task_id=task_id,
550
+ context_id=context_id,
551
+ status=TaskStatus(state=TaskState.working),
552
+ final=False,
553
+ metadata=metadata,
554
+ )
555
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
556
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
557
+
558
+ elif isinstance(event, LoopExecutionCompletedEvent):
559
+ metadata = {"agno_event_type": "loop_execution_completed"}
560
+ if hasattr(event, "step_name") and event.step_name:
561
+ metadata["step_name"] = event.step_name
562
+ if hasattr(event, "total_iterations") and event.total_iterations is not None:
563
+ metadata["total_iterations"] = event.total_iterations
564
+
565
+ status_event = TaskStatusUpdateEvent(
566
+ task_id=task_id,
567
+ context_id=context_id,
568
+ status=TaskStatus(state=TaskState.working),
569
+ final=False,
570
+ metadata=metadata,
571
+ )
572
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
573
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
574
+
575
+ # Send parallel events
576
+ elif isinstance(event, ParallelExecutionStartedEvent):
577
+ metadata = {"agno_event_type": "parallel_execution_started"}
578
+ if hasattr(event, "step_name") and event.step_name:
579
+ metadata["step_name"] = event.step_name
580
+ if hasattr(event, "parallel_step_count") and event.parallel_step_count:
581
+ metadata["parallel_step_count"] = event.parallel_step_count
582
+
583
+ status_event = TaskStatusUpdateEvent(
584
+ task_id=task_id,
585
+ context_id=context_id,
586
+ status=TaskStatus(state=TaskState.working),
587
+ final=False,
588
+ metadata=metadata,
589
+ )
590
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
591
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
592
+
593
+ elif isinstance(event, ParallelExecutionCompletedEvent):
594
+ metadata = {"agno_event_type": "parallel_execution_completed"}
595
+ if hasattr(event, "step_name") and event.step_name:
596
+ metadata["step_name"] = event.step_name
597
+ if hasattr(event, "parallel_step_count") and event.parallel_step_count:
598
+ metadata["parallel_step_count"] = event.parallel_step_count
599
+
600
+ status_event = TaskStatusUpdateEvent(
601
+ task_id=task_id,
602
+ context_id=context_id,
603
+ status=TaskStatus(state=TaskState.working),
604
+ final=False,
605
+ metadata=metadata,
606
+ )
607
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
608
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
609
+
610
+ # Send condition events
611
+ elif isinstance(event, ConditionExecutionStartedEvent):
612
+ metadata = {"agno_event_type": "condition_execution_started"}
613
+ if hasattr(event, "step_name") and event.step_name:
614
+ metadata["step_name"] = event.step_name
615
+ if hasattr(event, "condition_result") and event.condition_result is not None:
616
+ metadata["condition_result"] = event.condition_result
617
+
618
+ status_event = TaskStatusUpdateEvent(
619
+ task_id=task_id,
620
+ context_id=context_id,
621
+ status=TaskStatus(state=TaskState.working),
622
+ final=False,
623
+ metadata=metadata,
624
+ )
625
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
626
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
627
+
628
+ elif isinstance(event, ConditionExecutionCompletedEvent):
629
+ metadata = {"agno_event_type": "condition_execution_completed"}
630
+ if hasattr(event, "step_name") and event.step_name:
631
+ metadata["step_name"] = event.step_name
632
+ if hasattr(event, "condition_result") and event.condition_result is not None:
633
+ metadata["condition_result"] = event.condition_result
634
+ if hasattr(event, "executed_steps") and event.executed_steps is not None:
635
+ metadata["executed_steps"] = event.executed_steps
636
+
637
+ status_event = TaskStatusUpdateEvent(
638
+ task_id=task_id,
639
+ context_id=context_id,
640
+ status=TaskStatus(state=TaskState.working),
641
+ final=False,
642
+ metadata=metadata,
643
+ )
644
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
645
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
646
+
647
+ # Send router events
648
+ elif isinstance(event, RouterExecutionStartedEvent):
649
+ metadata = {"agno_event_type": "router_execution_started"}
650
+ if hasattr(event, "step_name") and event.step_name:
651
+ metadata["step_name"] = event.step_name
652
+ if hasattr(event, "selected_steps") and event.selected_steps:
653
+ metadata["selected_steps"] = event.selected_steps
654
+
655
+ status_event = TaskStatusUpdateEvent(
656
+ task_id=task_id,
657
+ context_id=context_id,
658
+ status=TaskStatus(state=TaskState.working),
659
+ final=False,
660
+ metadata=metadata,
661
+ )
662
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
663
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
664
+
665
+ elif isinstance(event, RouterExecutionCompletedEvent):
666
+ metadata = {"agno_event_type": "router_execution_completed"}
667
+ if hasattr(event, "step_name") and event.step_name:
668
+ metadata["step_name"] = event.step_name
669
+ if hasattr(event, "selected_steps") and event.selected_steps:
670
+ metadata["selected_steps"] = event.selected_steps
671
+ if hasattr(event, "executed_steps") and event.executed_steps is not None:
672
+ metadata["executed_steps"] = event.executed_steps
673
+
674
+ status_event = TaskStatusUpdateEvent(
675
+ task_id=task_id,
676
+ context_id=context_id,
677
+ status=TaskStatus(state=TaskState.working),
678
+ final=False,
679
+ metadata=metadata,
680
+ )
681
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
682
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
683
+
684
+ # Send steps events
685
+ elif isinstance(event, StepsExecutionStartedEvent):
686
+ metadata = {"agno_event_type": "steps_execution_started"}
687
+ if hasattr(event, "step_name") and event.step_name:
688
+ metadata["step_name"] = event.step_name
689
+ if hasattr(event, "steps_count") and event.steps_count:
690
+ metadata["steps_count"] = event.steps_count
691
+
692
+ status_event = TaskStatusUpdateEvent(
693
+ task_id=task_id,
694
+ context_id=context_id,
695
+ status=TaskStatus(state=TaskState.working),
696
+ final=False,
697
+ metadata=metadata,
698
+ )
699
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
700
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
701
+
702
+ elif isinstance(event, StepsExecutionCompletedEvent):
703
+ metadata = {"agno_event_type": "steps_execution_completed"}
704
+ if hasattr(event, "step_name") and event.step_name:
705
+ metadata["step_name"] = event.step_name
706
+ if hasattr(event, "steps_count") and event.steps_count:
707
+ metadata["steps_count"] = event.steps_count
708
+ if hasattr(event, "executed_steps") and event.executed_steps is not None:
709
+ metadata["executed_steps"] = event.executed_steps
710
+
711
+ status_event = TaskStatusUpdateEvent(
712
+ task_id=task_id,
713
+ context_id=context_id,
714
+ status=TaskStatus(state=TaskState.working),
715
+ final=False,
716
+ metadata=metadata,
717
+ )
718
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
719
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
720
+
721
+ # Capture completion event for final task construction
722
+ elif isinstance(event, (RunCompletedEvent, TeamRunCompletedEvent, WorkflowCompletedEvent)):
723
+ completion_event = event
724
+
725
+ # Capture cancelled event for final task construction
726
+ elif isinstance(event, (RunCancelledEvent, TeamRunCancelledEvent, WorkflowCancelledEvent)):
727
+ cancelled_event = event
728
+
729
+ # 3. Send final status event
730
+ # If cancelled, send canceled status; otherwise send completed
731
+ if cancelled_event:
732
+ final_state = TaskState.canceled
733
+ metadata = {"agno_event_type": "run_cancelled"}
734
+ if hasattr(cancelled_event, "reason") and cancelled_event.reason:
735
+ metadata["reason"] = cancelled_event.reason
736
+ final_status_event = TaskStatusUpdateEvent(
737
+ task_id=task_id,
738
+ context_id=context_id,
739
+ status=TaskStatus(state=final_state),
740
+ final=True,
741
+ metadata=metadata,
742
+ )
743
+ else:
744
+ final_status_event = TaskStatusUpdateEvent(
745
+ task_id=task_id,
746
+ context_id=context_id,
747
+ status=TaskStatus(state=TaskState.completed),
748
+ final=True,
749
+ )
750
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=final_status_event)
751
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
752
+
753
+ # 4. Send final task
754
+ # Handle cancelled case
755
+ if cancelled_event:
756
+ cancel_message = "Run was cancelled"
757
+ if hasattr(cancelled_event, "reason") and cancelled_event.reason:
758
+ cancel_message = f"Run was cancelled: {cancelled_event.reason}"
759
+
760
+ parts: List[Part] = []
761
+ if accumulated_content:
762
+ parts.append(Part(root=TextPart(text=accumulated_content)))
763
+ parts.append(Part(root=TextPart(text=cancel_message)))
764
+
765
+ final_message = A2AMessage(
766
+ message_id=message_id,
767
+ role=Role.agent,
768
+ parts=parts,
769
+ context_id=context_id,
770
+ task_id=task_id,
771
+ metadata={"agno_event_type": "run_cancelled"},
772
+ )
773
+
774
+ task = Task(
775
+ id=task_id,
776
+ context_id=context_id,
777
+ status=TaskStatus(state=TaskState.canceled),
778
+ history=[final_message],
779
+ )
780
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=task)
781
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
782
+ return
783
+
784
+ # Build from completion_event if available, otherwise use accumulated content
785
+ if completion_event:
786
+ final_content = completion_event.content if completion_event.content else accumulated_content
787
+
788
+ final_parts: List[Part] = []
789
+ if final_content:
790
+ final_parts.append(Part(root=TextPart(text=str(final_content))))
791
+
792
+ # Handle all media artifacts
793
+ artifacts: List[Artifact] = []
794
+ if hasattr(completion_event, "images") and completion_event.images:
795
+ for idx, image in enumerate(completion_event.images):
796
+ artifact_parts = []
797
+ if image.url:
798
+ artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=image.url, mime_type="image/*"))))
799
+ artifacts.append(
800
+ Artifact(
801
+ artifact_id=f"image-{idx}",
802
+ name=getattr(image, "name", None) or f"image-{idx}",
803
+ description="Image generated during task",
804
+ parts=artifact_parts,
805
+ )
806
+ )
807
+ if hasattr(completion_event, "videos") and completion_event.videos:
808
+ for idx, video in enumerate(completion_event.videos):
809
+ artifact_parts = []
810
+ if video.url:
811
+ artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=video.url, mime_type="video/*"))))
812
+ artifacts.append(
813
+ Artifact(
814
+ artifact_id=f"video-{idx}",
815
+ name=getattr(video, "name", None) or f"video-{idx}",
816
+ description="Video generated during task",
817
+ parts=artifact_parts,
818
+ )
819
+ )
820
+ if hasattr(completion_event, "audio") and completion_event.audio:
821
+ for idx, audio in enumerate(completion_event.audio):
822
+ artifact_parts = []
823
+ if audio.url:
824
+ artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=audio.url, mime_type="audio/*"))))
825
+ artifacts.append(
826
+ Artifact(
827
+ artifact_id=f"audio-{idx}",
828
+ name=getattr(audio, "name", None) or f"audio-{idx}",
829
+ description="Audio generated during task",
830
+ parts=artifact_parts,
831
+ )
832
+ )
833
+ if hasattr(completion_event, "response_audio") and completion_event.response_audio:
834
+ audio = completion_event.response_audio
835
+ artifact_parts = []
836
+ if audio.url:
837
+ artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=audio.url, mime_type="audio/*"))))
838
+ artifacts.append(
839
+ Artifact(
840
+ artifact_id="response-audio",
841
+ name=getattr(audio, "name", None) or "response-audio",
842
+ description="Audio response from agent",
843
+ parts=artifact_parts,
844
+ )
845
+ )
846
+
847
+ # Handle all other data as Message metadata
848
+ final_metadata: Dict[str, Any] = {}
849
+ if hasattr(completion_event, "metrics") and completion_event.metrics:
850
+ final_metadata["metrics"] = completion_event.metrics.__dict__
851
+ if hasattr(completion_event, "metadata") and completion_event.metadata:
852
+ final_metadata.update(completion_event.metadata)
853
+
854
+ final_message = A2AMessage(
855
+ message_id=message_id,
856
+ role=Role.agent,
857
+ parts=final_parts,
858
+ context_id=context_id,
859
+ task_id=task_id,
860
+ metadata=final_metadata if final_metadata else None,
861
+ )
862
+
863
+ else:
864
+ # Fallback in case we didn't find the completion event, using accumulated content
865
+ final_message = A2AMessage(
866
+ message_id=message_id,
867
+ role=Role.agent,
868
+ parts=[Part(root=TextPart(text=accumulated_content))] if accumulated_content else [],
869
+ context_id=context_id,
870
+ task_id=task_id,
871
+ )
872
+ artifacts = []
873
+
874
+ # Build and return the final Task
875
+ task = Task(
876
+ id=task_id,
877
+ context_id=context_id,
878
+ status=TaskStatus(state=TaskState.completed),
879
+ history=[final_message],
880
+ artifacts=artifacts if artifacts else None,
881
+ )
882
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=task)
883
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
884
+
885
+
886
+ async def stream_a2a_response_with_error_handling(
887
+ event_stream: AsyncIterator[Union[RunOutputEvent, TeamRunOutputEvent, WorkflowRunOutputEvent, RunOutput]],
888
+ request_id: Union[str, int],
889
+ ) -> AsyncIterator[str]:
890
+ """Wrapper around stream_a2a_response to handle critical errors."""
891
+ task_id: str = str(uuid4())
892
+ context_id: str = str(uuid4())
893
+
894
+ try:
895
+ async for response_chunk in stream_a2a_response(event_stream, request_id):
896
+ yield response_chunk
897
+
898
+ # Catch any critical errors, emit the expected status task and close the stream
899
+ except Exception as e:
900
+ failed_status_event = TaskStatusUpdateEvent(
901
+ task_id=task_id,
902
+ context_id=context_id,
903
+ status=TaskStatus(state=TaskState.failed),
904
+ final=True,
905
+ )
906
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=failed_status_event)
907
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
908
+
909
+ # Send failed Task
910
+ error_message = A2AMessage(
911
+ message_id=str(uuid4()),
912
+ role=Role.agent,
913
+ parts=[Part(root=TextPart(text=f"Error: {str(e)}"))],
914
+ context_id=context_id,
915
+ )
916
+ failed_task = Task(
917
+ id=task_id,
918
+ context_id=context_id,
919
+ status=TaskStatus(state=TaskState.failed),
920
+ history=[error_message],
921
+ )
922
+
923
+ response = SendStreamingMessageSuccessResponse(id=request_id, result=failed_task)
924
+ yield json.dumps(response.model_dump(exclude_none=True)) + "\n"