agno 2.3.12__py3-none-any.whl → 2.3.14__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 (55) hide show
  1. agno/agent/agent.py +1125 -1401
  2. agno/eval/__init__.py +21 -8
  3. agno/knowledge/embedder/azure_openai.py +0 -1
  4. agno/knowledge/embedder/google.py +1 -1
  5. agno/models/anthropic/claude.py +4 -1
  6. agno/models/azure/openai_chat.py +11 -5
  7. agno/models/base.py +8 -4
  8. agno/models/openai/chat.py +0 -2
  9. agno/models/openai/responses.py +2 -2
  10. agno/os/app.py +112 -5
  11. agno/os/auth.py +190 -3
  12. agno/os/config.py +9 -0
  13. agno/os/interfaces/a2a/router.py +619 -9
  14. agno/os/interfaces/a2a/utils.py +31 -32
  15. agno/os/middleware/__init__.py +2 -0
  16. agno/os/middleware/jwt.py +670 -108
  17. agno/os/router.py +0 -1
  18. agno/os/routers/agents/router.py +22 -4
  19. agno/os/routers/agents/schema.py +14 -1
  20. agno/os/routers/teams/router.py +20 -4
  21. agno/os/routers/teams/schema.py +14 -1
  22. agno/os/routers/workflows/router.py +88 -9
  23. agno/os/scopes.py +469 -0
  24. agno/os/utils.py +86 -53
  25. agno/reasoning/anthropic.py +85 -1
  26. agno/reasoning/azure_ai_foundry.py +93 -1
  27. agno/reasoning/deepseek.py +91 -1
  28. agno/reasoning/gemini.py +81 -1
  29. agno/reasoning/groq.py +103 -1
  30. agno/reasoning/manager.py +1244 -0
  31. agno/reasoning/ollama.py +93 -1
  32. agno/reasoning/openai.py +113 -1
  33. agno/reasoning/vertexai.py +85 -1
  34. agno/run/agent.py +11 -0
  35. agno/run/base.py +1 -1
  36. agno/run/team.py +11 -0
  37. agno/session/team.py +0 -3
  38. agno/team/team.py +1204 -1452
  39. agno/tools/postgres.py +1 -1
  40. agno/utils/cryptography.py +22 -0
  41. agno/utils/events.py +69 -2
  42. agno/utils/hooks.py +4 -10
  43. agno/utils/print_response/agent.py +52 -2
  44. agno/utils/print_response/team.py +141 -10
  45. agno/utils/prompts.py +8 -6
  46. agno/utils/string.py +46 -0
  47. agno/utils/team.py +1 -1
  48. agno/vectordb/chroma/chromadb.py +1 -0
  49. agno/vectordb/milvus/milvus.py +32 -3
  50. agno/vectordb/redis/redisdb.py +16 -2
  51. {agno-2.3.12.dist-info → agno-2.3.14.dist-info}/METADATA +3 -2
  52. {agno-2.3.12.dist-info → agno-2.3.14.dist-info}/RECORD +55 -52
  53. {agno-2.3.12.dist-info → agno-2.3.14.dist-info}/WHEEL +0 -0
  54. {agno-2.3.12.dist-info → agno-2.3.14.dist-info}/licenses/LICENSE +0 -0
  55. {agno-2.3.12.dist-info → agno-2.3.14.dist-info}/top_level.txt +0 -0
@@ -9,10 +9,20 @@ from fastapi.routing import APIRouter
9
9
  from typing_extensions import List
10
10
 
11
11
  try:
12
- from a2a.types import SendMessageSuccessResponse, Task, TaskState, TaskStatus
12
+ from a2a.types import (
13
+ AgentCapabilities,
14
+ AgentCard,
15
+ AgentSkill,
16
+ SendMessageSuccessResponse,
17
+ Task,
18
+ TaskState,
19
+ TaskStatus,
20
+ )
13
21
  except ImportError as e:
14
22
  raise ImportError("`a2a` not installed. Please install it with `pip install -U a2a-sdk`") from e
15
23
 
24
+ import warnings
25
+
16
26
  from agno.agent import Agent
17
27
  from agno.os.interfaces.a2a.utils import (
18
28
  map_a2a_request_to_run_input,
@@ -33,11 +43,599 @@ def attach_routes(
33
43
  if agents is None and teams is None and workflows is None:
34
44
  raise ValueError("Agents, Teams, or Workflows are required to setup the A2A interface.")
35
45
 
46
+ # ============= AGENTS =============
47
+ @router.get("/agents/{id}/.well-known/agent-card.json")
48
+ async def get_agent_card(request: Request, id: str):
49
+ agent = get_agent_by_id(id, agents)
50
+ if not agent:
51
+ raise HTTPException(status_code=404, detail="Agent not found")
52
+
53
+ base_url = str(request.base_url).rstrip("/")
54
+ skill = AgentSkill(
55
+ id=agent.id or "",
56
+ name=agent.name or "",
57
+ description=agent.description or "",
58
+ tags=["agno"],
59
+ examples=["search", "ok"],
60
+ output_modes=["application/json"],
61
+ )
62
+
63
+ return AgentCard(
64
+ name=agent.name or "",
65
+ version="1.0.0",
66
+ description=agent.description or "",
67
+ url=f"{base_url}/a2a/agents/{agent.id}/v1/message:stream",
68
+ default_input_modes=["text"],
69
+ default_output_modes=["text"],
70
+ capabilities=AgentCapabilities(streaming=True, push_notifications=False, state_transition_history=False),
71
+ skills=[skill],
72
+ supports_authenticated_extended_card=False,
73
+ )
74
+
75
+ @router.post(
76
+ "/agents/{id}/v1/message:send",
77
+ operation_id="run_message_agent",
78
+ name="run_message_agent",
79
+ description="Send a message to an Agno Agent (non-streaming). The Agent is identified via the path parameter '{id}'. "
80
+ "Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata.",
81
+ response_model_exclude_none=True,
82
+ responses={
83
+ 200: {
84
+ "description": "Message sent successfully",
85
+ "content": {
86
+ "application/json": {
87
+ "example": {
88
+ "jsonrpc": "2.0",
89
+ "id": "request-123",
90
+ "result": {
91
+ "task": {
92
+ "id": "task-456",
93
+ "context_id": "context-789",
94
+ "status": "completed",
95
+ "history": [
96
+ {
97
+ "message_id": "msg-1",
98
+ "role": "agent",
99
+ "parts": [{"kind": "text", "text": "Response from agent"}],
100
+ }
101
+ ],
102
+ }
103
+ },
104
+ }
105
+ }
106
+ },
107
+ },
108
+ 400: {"description": "Invalid request"},
109
+ 404: {"description": "Agent not found"},
110
+ },
111
+ response_model=SendMessageSuccessResponse,
112
+ )
113
+ async def a2a_run_agent(request: Request, id: str):
114
+ if not agents:
115
+ raise HTTPException(status_code=404, detail="Agent not found")
116
+
117
+ # Load the request body. Unknown args are passed down as kwargs.
118
+ request_body = await request.json()
119
+ kwargs = await get_request_kwargs(request, a2a_run_agent)
120
+
121
+ # 1. Get the Agent to run
122
+ agent = get_agent_by_id(id, agents)
123
+ if not agent:
124
+ raise HTTPException(status_code=404, detail="Agent not found")
125
+
126
+ # 2. Map the request to our run_input and run variables
127
+ run_input = await map_a2a_request_to_run_input(request_body, stream=False)
128
+ context_id = request_body.get("params", {}).get("message", {}).get("contextId")
129
+ user_id = request.headers.get("X-User-ID")
130
+ if not user_id:
131
+ user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
132
+
133
+ # 3. Run the Agent
134
+ try:
135
+ response = await agent.arun(
136
+ input=run_input.input_content,
137
+ images=run_input.images,
138
+ videos=run_input.videos,
139
+ audio=run_input.audios,
140
+ files=run_input.files,
141
+ session_id=context_id,
142
+ user_id=user_id,
143
+ **kwargs,
144
+ )
145
+
146
+ # 4. Send the response
147
+ a2a_task = map_run_output_to_a2a_task(response)
148
+ return SendMessageSuccessResponse(
149
+ id=request_body.get("id", "unknown"),
150
+ result=a2a_task,
151
+ )
152
+
153
+ # Handle any critical error
154
+ except Exception as e:
155
+ from a2a.types import Message as A2AMessage
156
+ from a2a.types import Part, Role, TextPart
157
+
158
+ error_message = A2AMessage(
159
+ message_id=str(uuid4()),
160
+ role=Role.agent,
161
+ parts=[Part(root=TextPart(text=f"Error: {str(e)}"))],
162
+ context_id=context_id or str(uuid4()),
163
+ )
164
+ failed_task = Task(
165
+ id=str(uuid4()),
166
+ context_id=context_id or str(uuid4()),
167
+ status=TaskStatus(state=TaskState.failed),
168
+ history=[error_message],
169
+ )
170
+
171
+ return SendMessageSuccessResponse(
172
+ id=request_body.get("id", "unknown"),
173
+ result=failed_task,
174
+ )
175
+
176
+ @router.post(
177
+ "/agents/{id}/v1/message:stream",
178
+ operation_id="stream_message_agent",
179
+ name="stream_message_agent",
180
+ description="Stream a message to an Agno Agent (streaming). The Agent is identified via the path parameter '{id}'. "
181
+ "Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata. "
182
+ "Returns real-time updates as newline-delimited JSON (NDJSON).",
183
+ response_model_exclude_none=True,
184
+ responses={
185
+ 200: {
186
+ "description": "Streaming response with task updates",
187
+ "content": {
188
+ "text/event-stream": {
189
+ "example": 'event: TaskStatusUpdateEvent\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"taskId":"task-456","status":"working"}}\n\n'
190
+ 'event: Message\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"messageId":"msg-1","role":"agent","parts":[{"kind":"text","text":"Response"}]}}\n\n'
191
+ }
192
+ },
193
+ },
194
+ 400: {"description": "Invalid request"},
195
+ 404: {"description": "Agent not found"},
196
+ },
197
+ )
198
+ async def a2a_stream_agent(request: Request, id: str):
199
+ if not agents:
200
+ raise HTTPException(status_code=404, detail="Agent not found")
201
+
202
+ # Load the request body. Unknown args are passed down as kwargs.
203
+ request_body = await request.json()
204
+ kwargs = await get_request_kwargs(request, a2a_stream_agent)
205
+
206
+ # 1. Get the Agent to run
207
+ agent = get_agent_by_id(id, agents)
208
+ if not agent:
209
+ raise HTTPException(status_code=404, detail="Agent not found")
210
+
211
+ # 2. Map the request to our run_input and run variables
212
+ run_input = await map_a2a_request_to_run_input(request_body, stream=True)
213
+ context_id = request_body.get("params", {}).get("message", {}).get("contextId")
214
+ user_id = request.headers.get("X-User-ID")
215
+ if not user_id:
216
+ user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
217
+
218
+ # 3. Run the Agent and stream the response
219
+ try:
220
+ event_stream = agent.arun(
221
+ input=run_input.input_content,
222
+ images=run_input.images,
223
+ videos=run_input.videos,
224
+ audio=run_input.audios,
225
+ files=run_input.files,
226
+ session_id=context_id,
227
+ user_id=user_id,
228
+ stream=True,
229
+ stream_events=True,
230
+ **kwargs,
231
+ )
232
+
233
+ # 4. Stream the response
234
+ return StreamingResponse(
235
+ stream_a2a_response_with_error_handling(event_stream=event_stream, request_id=request_body["id"]), # type: ignore[arg-type]
236
+ media_type="text/event-stream",
237
+ )
238
+
239
+ except Exception as e:
240
+ raise HTTPException(status_code=500, detail=f"Failed to start run: {str(e)}")
241
+
242
+ # ============= TEAMS =============
243
+ @router.get("/teams/{id}/.well-known/agent-card.json")
244
+ async def get_team_card(request: Request, id: str):
245
+ team = get_team_by_id(id, teams)
246
+ if not team:
247
+ raise HTTPException(status_code=404, detail="Team not found")
248
+
249
+ base_url = str(request.base_url).rstrip("/")
250
+ skill = AgentSkill(
251
+ id=team.id or "",
252
+ name=team.name or "",
253
+ description=team.description or "",
254
+ tags=["agno"],
255
+ examples=["search", "ok"],
256
+ output_modes=["application/json"],
257
+ )
258
+ return AgentCard(
259
+ name=team.name or "",
260
+ version="1.0.0",
261
+ description=team.description or "",
262
+ url=f"{base_url}/a2a/teams/{team.id}/v1/message:stream",
263
+ default_input_modes=["text"],
264
+ default_output_modes=["text"],
265
+ capabilities=AgentCapabilities(streaming=True, push_notifications=False, state_transition_history=False),
266
+ skills=[skill],
267
+ supports_authenticated_extended_card=False,
268
+ )
269
+
270
+ @router.post(
271
+ "/teams/{id}/v1/message:send",
272
+ operation_id="run_message_team",
273
+ name="run_message_team",
274
+ description="Send a message to an Agno Team (non-streaming). The Team is identified via the path parameter '{id}'. "
275
+ "Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata.",
276
+ response_model_exclude_none=True,
277
+ responses={
278
+ 200: {
279
+ "description": "Message sent successfully",
280
+ "content": {
281
+ "application/json": {
282
+ "example": {
283
+ "jsonrpc": "2.0",
284
+ "id": "request-123",
285
+ "result": {
286
+ "task": {
287
+ "id": "task-456",
288
+ "context_id": "context-789",
289
+ "status": "completed",
290
+ "history": [
291
+ {
292
+ "message_id": "msg-1",
293
+ "role": "agent",
294
+ "parts": [{"kind": "text", "text": "Response from agent"}],
295
+ }
296
+ ],
297
+ }
298
+ },
299
+ }
300
+ }
301
+ },
302
+ },
303
+ 400: {"description": "Invalid request"},
304
+ 404: {"description": "Team not found"},
305
+ },
306
+ response_model=SendMessageSuccessResponse,
307
+ )
308
+ async def a2a_run_team(request: Request, id: str):
309
+ if not teams:
310
+ raise HTTPException(status_code=404, detail="Team not found")
311
+
312
+ # Load the request body. Unknown args are passed down as kwargs.
313
+ request_body = await request.json()
314
+ kwargs = await get_request_kwargs(request, a2a_run_team)
315
+
316
+ # 1. Get the Team to run
317
+ team = get_team_by_id(id, teams)
318
+ if not team:
319
+ raise HTTPException(status_code=404, detail="Team not found")
320
+
321
+ # 2. Map the request to our run_input and run variables
322
+ run_input = await map_a2a_request_to_run_input(request_body, stream=False)
323
+ context_id = request_body.get("params", {}).get("message", {}).get("contextId")
324
+ user_id = request.headers.get("X-User-ID")
325
+ if not user_id:
326
+ user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
327
+
328
+ # 3. Run the Team
329
+ try:
330
+ response = await team.arun(
331
+ input=run_input.input_content,
332
+ images=run_input.images,
333
+ videos=run_input.videos,
334
+ audio=run_input.audios,
335
+ files=run_input.files,
336
+ session_id=context_id,
337
+ user_id=user_id,
338
+ **kwargs,
339
+ )
340
+
341
+ # 4. Send the response
342
+ a2a_task = map_run_output_to_a2a_task(response)
343
+ return SendMessageSuccessResponse(
344
+ id=request_body.get("id", "unknown"),
345
+ result=a2a_task,
346
+ )
347
+
348
+ # Handle all critical errors
349
+ except Exception as e:
350
+ from a2a.types import Message as A2AMessage
351
+ from a2a.types import Part, Role, TextPart
352
+
353
+ error_message = A2AMessage(
354
+ message_id=str(uuid4()),
355
+ role=Role.agent,
356
+ parts=[Part(root=TextPart(text=f"Error: {str(e)}"))],
357
+ context_id=context_id or str(uuid4()),
358
+ )
359
+ failed_task = Task(
360
+ id=str(uuid4()),
361
+ context_id=context_id or str(uuid4()),
362
+ status=TaskStatus(state=TaskState.failed),
363
+ history=[error_message],
364
+ )
365
+
366
+ return SendMessageSuccessResponse(
367
+ id=request_body.get("id", "unknown"),
368
+ result=failed_task,
369
+ )
370
+
371
+ @router.post(
372
+ "/teams/{id}/v1/message:stream",
373
+ operation_id="stream_message_team",
374
+ name="stream_message_team",
375
+ description="Stream a message to an Agno Team (streaming). The Team is identified via the path parameter '{id}'. "
376
+ "Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata. "
377
+ "Returns real-time updates as newline-delimited JSON (NDJSON).",
378
+ response_model_exclude_none=True,
379
+ responses={
380
+ 200: {
381
+ "description": "Streaming response with task updates",
382
+ "content": {
383
+ "text/event-stream": {
384
+ "example": 'event: TaskStatusUpdateEvent\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"taskId":"task-456","status":"working"}}\n\n'
385
+ 'event: Message\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"messageId":"msg-1","role":"agent","parts":[{"kind":"text","text":"Response"}]}}\n\n'
386
+ }
387
+ },
388
+ },
389
+ 400: {"description": "Invalid request"},
390
+ 404: {"description": "Team not found"},
391
+ },
392
+ )
393
+ async def a2a_stream_team(request: Request, id: str):
394
+ if not teams:
395
+ raise HTTPException(status_code=404, detail="Team not found")
396
+
397
+ # Load the request body. Unknown args are passed down as kwargs.
398
+ request_body = await request.json()
399
+ kwargs = await get_request_kwargs(request, a2a_stream_team)
400
+
401
+ # 1. Get the Team to run
402
+ team = get_team_by_id(id, teams)
403
+ if not team:
404
+ raise HTTPException(status_code=404, detail="Team not found")
405
+
406
+ # 2. Map the request to our run_input and run variables
407
+ run_input = await map_a2a_request_to_run_input(request_body, stream=True)
408
+ context_id = request_body.get("params", {}).get("message", {}).get("contextId")
409
+ user_id = request.headers.get("X-User-ID")
410
+ if not user_id:
411
+ user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
412
+
413
+ # 3. Run the Team and stream the response
414
+ try:
415
+ event_stream = team.arun(
416
+ input=run_input.input_content,
417
+ images=run_input.images,
418
+ videos=run_input.videos,
419
+ audio=run_input.audios,
420
+ files=run_input.files,
421
+ session_id=context_id,
422
+ user_id=user_id,
423
+ stream=True,
424
+ stream_events=True,
425
+ **kwargs,
426
+ )
427
+
428
+ # 4. Stream the response
429
+ return StreamingResponse(
430
+ stream_a2a_response_with_error_handling(event_stream=event_stream, request_id=request_body["id"]), # type: ignore[arg-type]
431
+ media_type="text/event-stream",
432
+ )
433
+
434
+ except Exception as e:
435
+ raise HTTPException(status_code=500, detail=f"Failed to start run: {str(e)}")
436
+
437
+ # ============= WORKFLOWS =============
438
+ @router.get("/workflows/{id}/.well-known/agent-card.json")
439
+ async def get_workflow_card(request: Request, id: str):
440
+ workflow = get_workflow_by_id(id, workflows)
441
+ if not workflow:
442
+ raise HTTPException(status_code=404, detail="Workflow not found")
443
+
444
+ base_url = str(request.base_url).rstrip("/")
445
+ skill = AgentSkill(
446
+ id=workflow.id or "",
447
+ name=workflow.name or "",
448
+ description=workflow.description or "",
449
+ tags=["agno"],
450
+ examples=["search", "ok"],
451
+ output_modes=["application/json"],
452
+ )
453
+ return AgentCard(
454
+ name=workflow.name or "",
455
+ version="1.0.0",
456
+ description=workflow.description or "",
457
+ url=f"{base_url}/a2a/workflows/{workflow.id}/v1/message:stream",
458
+ default_input_modes=["text"],
459
+ default_output_modes=["text"],
460
+ capabilities=AgentCapabilities(streaming=False, push_notifications=False, state_transition_history=False),
461
+ skills=[skill],
462
+ supports_authenticated_extended_card=False,
463
+ )
464
+
465
+ @router.post(
466
+ "/workflows/{id}/v1/message:send",
467
+ operation_id="run_message_workflow",
468
+ name="run_message_workflow",
469
+ description="Send a message to an Agno Workflow (non-streaming). The Workflow is identified via the path parameter '{id}'. "
470
+ "Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata.",
471
+ response_model_exclude_none=True,
472
+ responses={
473
+ 200: {
474
+ "description": "Message sent successfully",
475
+ "content": {
476
+ "application/json": {
477
+ "example": {
478
+ "jsonrpc": "2.0",
479
+ "id": "request-123",
480
+ "result": {
481
+ "task": {
482
+ "id": "task-456",
483
+ "context_id": "context-789",
484
+ "status": "completed",
485
+ "history": [
486
+ {
487
+ "message_id": "msg-1",
488
+ "role": "agent",
489
+ "parts": [{"kind": "text", "text": "Response from agent"}],
490
+ }
491
+ ],
492
+ }
493
+ },
494
+ }
495
+ }
496
+ },
497
+ },
498
+ 400: {"description": "Invalid request"},
499
+ 404: {"description": "Workflow not found"},
500
+ },
501
+ response_model=SendMessageSuccessResponse,
502
+ )
503
+ async def a2a_run_workflow(request: Request, id: str):
504
+ if not workflows:
505
+ raise HTTPException(status_code=404, detail="Workflow not found")
506
+
507
+ # Load the request body. Unknown args are passed down as kwargs.
508
+ request_body = await request.json()
509
+ kwargs = await get_request_kwargs(request, a2a_run_workflow)
510
+
511
+ # 1. Get the Workflow to run
512
+ workflow = get_workflow_by_id(id, workflows)
513
+ if not workflow:
514
+ raise HTTPException(status_code=404, detail="Workflow not found")
515
+
516
+ # 2. Map the request to our run_input and run variables
517
+ run_input = await map_a2a_request_to_run_input(request_body, stream=False)
518
+ context_id = request_body.get("params", {}).get("message", {}).get("contextId")
519
+ user_id = request.headers.get("X-User-ID")
520
+ if not user_id:
521
+ user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
522
+
523
+ # 3. Run the Workflow
524
+ try:
525
+ response = await workflow.arun(
526
+ input=run_input.input_content,
527
+ images=list(run_input.images) if run_input.images else None,
528
+ videos=list(run_input.videos) if run_input.videos else None,
529
+ audio=list(run_input.audios) if run_input.audios else None,
530
+ files=list(run_input.files) if run_input.files else None,
531
+ session_id=context_id,
532
+ user_id=user_id,
533
+ **kwargs,
534
+ )
535
+
536
+ # 4. Send the response
537
+ a2a_task = map_run_output_to_a2a_task(response)
538
+ return SendMessageSuccessResponse(
539
+ id=request_body.get("id", "unknown"),
540
+ result=a2a_task,
541
+ )
542
+
543
+ # Handle all critical errors
544
+ except Exception as e:
545
+ from a2a.types import Message as A2AMessage
546
+ from a2a.types import Part, Role, TextPart
547
+
548
+ error_message = A2AMessage(
549
+ message_id=str(uuid4()),
550
+ role=Role.agent,
551
+ parts=[Part(root=TextPart(text=f"Error: {str(e)}"))],
552
+ context_id=context_id or str(uuid4()),
553
+ )
554
+ failed_task = Task(
555
+ id=str(uuid4()),
556
+ context_id=context_id or str(uuid4()),
557
+ status=TaskStatus(state=TaskState.failed),
558
+ history=[error_message],
559
+ )
560
+
561
+ return SendMessageSuccessResponse(
562
+ id=request_body.get("id", "unknown"),
563
+ result=failed_task,
564
+ )
565
+
566
+ @router.post(
567
+ "/workflows/{id}/v1/message:stream",
568
+ operation_id="stream_message_workflow",
569
+ name="stream_message_workflow",
570
+ description="Stream a message to an Agno Workflow (streaming). The Workflow is identified via the path parameter '{id}'. "
571
+ "Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata. "
572
+ "Returns real-time updates as newline-delimited JSON (NDJSON).",
573
+ response_model_exclude_none=True,
574
+ responses={
575
+ 200: {
576
+ "description": "Streaming response with task updates",
577
+ "content": {
578
+ "text/event-stream": {
579
+ "example": 'event: TaskStatusUpdateEvent\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"taskId":"task-456","status":"working"}}\n\n'
580
+ 'event: Message\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"messageId":"msg-1","role":"agent","parts":[{"kind":"text","text":"Response"}]}}\n\n'
581
+ }
582
+ },
583
+ },
584
+ 400: {"description": "Invalid request"},
585
+ 404: {"description": "Workflow not found"},
586
+ },
587
+ )
588
+ async def a2a_stream_workflow(request: Request, id: str):
589
+ if not workflows:
590
+ raise HTTPException(status_code=404, detail="Workflow not found")
591
+
592
+ # Load the request body. Unknown args are passed down as kwargs.
593
+ request_body = await request.json()
594
+ kwargs = await get_request_kwargs(request, a2a_stream_workflow)
595
+
596
+ # 1. Get the Workflow to run
597
+ workflow = get_workflow_by_id(id, workflows)
598
+ if not workflow:
599
+ raise HTTPException(status_code=404, detail="Workflow not found")
600
+
601
+ # 2. Map the request to our run_input and run variables
602
+ run_input = await map_a2a_request_to_run_input(request_body, stream=True)
603
+ context_id = request_body.get("params", {}).get("message", {}).get("contextId")
604
+ user_id = request.headers.get("X-User-ID")
605
+ if not user_id:
606
+ user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
607
+
608
+ # 3. Run the Workflow and stream the response
609
+ try:
610
+ event_stream = workflow.arun(
611
+ input=run_input.input_content,
612
+ images=list(run_input.images) if run_input.images else None,
613
+ videos=list(run_input.videos) if run_input.videos else None,
614
+ audio=list(run_input.audios) if run_input.audios else None,
615
+ files=list(run_input.files) if run_input.files else None,
616
+ session_id=context_id,
617
+ user_id=user_id,
618
+ stream=True,
619
+ stream_events=True,
620
+ **kwargs,
621
+ )
622
+
623
+ # 4. Stream the response
624
+ return StreamingResponse(
625
+ stream_a2a_response_with_error_handling(event_stream=event_stream, request_id=request_body["id"]), # type: ignore[arg-type]
626
+ media_type="text/event-stream",
627
+ )
628
+
629
+ except Exception as e:
630
+ raise HTTPException(status_code=500, detail=f"Failed to start run: {str(e)}")
631
+
632
+ # ============= DEPRECATED ENDPOINTS =============
633
+
36
634
  @router.post(
37
635
  "/message/send",
38
636
  operation_id="send_message",
39
637
  name="send_message",
40
- description="Send a message to an Agno Agent, Team, or Workflow. "
638
+ description="[DEPRECATED] Send a message to an Agno Agent, Team, or Workflow. "
41
639
  "The Agent, Team or Workflow is identified via the 'agentId' field in params.message or X-Agent-ID header. "
42
640
  "Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata.",
43
641
  response_model_exclude_none=True,
@@ -73,6 +671,12 @@ def attach_routes(
73
671
  response_model=SendMessageSuccessResponse,
74
672
  )
75
673
  async def a2a_send_message(request: Request):
674
+ warnings.warn(
675
+ "This endpoint will be deprecated soon. Use /agents/{agents_id}/v1/message:send, /teams/{teams_id}/v1/message:send, or /workflows/{workflows_id}/v1/message:send instead.",
676
+ DeprecationWarning,
677
+ )
678
+
679
+ # Load the request body. Unknown args are passed down as kwargs.
76
680
  request_body = await request.json()
77
681
  kwargs = await get_request_kwargs(request, a2a_send_message)
78
682
 
@@ -103,7 +707,7 @@ def attach_routes(
103
707
  # 3. Run the agent, team, or workflow
104
708
  try:
105
709
  if isinstance(entity, Workflow):
106
- response = await entity.arun(
710
+ response = entity.arun(
107
711
  input=run_input.input_content,
108
712
  images=list(run_input.images) if run_input.images else None,
109
713
  videos=list(run_input.videos) if run_input.videos else None,
@@ -114,7 +718,7 @@ def attach_routes(
114
718
  **kwargs,
115
719
  )
116
720
  else:
117
- response = await entity.arun(
721
+ response = entity.arun(
118
722
  input=run_input.input_content,
119
723
  images=run_input.images,
120
724
  videos=run_input.videos,
@@ -159,7 +763,7 @@ def attach_routes(
159
763
  "/message/stream",
160
764
  operation_id="stream_message",
161
765
  name="stream_message",
162
- description="Stream a message to an Agno Agent, Team, or Workflow."
766
+ description="[DEPRECATED] Stream a message to an Agno Agent, Team, or Workflow. "
163
767
  "The Agent, Team or Workflow is identified via the 'agentId' field in params.message or X-Agent-ID header. "
164
768
  "Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata. "
165
769
  "Returns real-time updates as newline-delimited JSON (NDJSON).",
@@ -168,9 +772,9 @@ def attach_routes(
168
772
  200: {
169
773
  "description": "Streaming response with task updates",
170
774
  "content": {
171
- "application/x-ndjson": {
172
- "example": '{"jsonrpc":"2.0","id":"request-123","result":{"taskId":"task-456","status":"working"}}\n'
173
- '{"jsonrpc":"2.0","id":"request-123","result":{"messageId":"msg-1","role":"agent","parts":[{"kind":"text","text":"Response"}]}}\n'
775
+ "text/event-stream": {
776
+ "example": 'event: TaskStatusUpdateEvent\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"taskId":"task-456","status":"working"}}\n\n'
777
+ 'event: Message\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"messageId":"msg-1","role":"agent","parts":[{"kind":"text","text":"Response"}]}}\n\n'
174
778
  }
175
779
  },
176
780
  },
@@ -179,6 +783,12 @@ def attach_routes(
179
783
  },
180
784
  )
181
785
  async def a2a_stream_message(request: Request):
786
+ warnings.warn(
787
+ "This endpoint will be deprecated soon. Use /agents/{agents_id}/v1/message:stream, /teams/{teams_id}/v1/message:stream, or /workflows/{workflows_id}/v1/message:stream instead.",
788
+ DeprecationWarning,
789
+ )
790
+
791
+ # Load the request body. Unknown args are passed down as kwargs.
182
792
  request_body = await request.json()
183
793
  kwargs = await get_request_kwargs(request, a2a_stream_message)
184
794
 
@@ -240,7 +850,7 @@ def attach_routes(
240
850
  # 4. Stream the response
241
851
  return StreamingResponse(
242
852
  stream_a2a_response_with_error_handling(event_stream=event_stream, request_id=request_body["id"]), # type: ignore[arg-type]
243
- media_type="application/x-ndjson",
853
+ media_type="text/event-stream",
244
854
  )
245
855
 
246
856
  except Exception as e: