agno 2.0.0a1__py3-none-any.whl → 2.0.0rc1__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 (50) hide show
  1. agno/agent/agent.py +390 -33
  2. agno/api/agent.py +2 -2
  3. agno/api/evals.py +2 -2
  4. agno/api/os.py +1 -1
  5. agno/api/settings.py +2 -2
  6. agno/api/team.py +2 -2
  7. agno/db/dynamo/dynamo.py +0 -6
  8. agno/db/firestore/firestore.py +0 -6
  9. agno/db/in_memory/in_memory_db.py +0 -6
  10. agno/db/json/json_db.py +0 -6
  11. agno/db/mongo/mongo.py +0 -6
  12. agno/db/mysql/utils.py +0 -1
  13. agno/db/postgres/postgres.py +0 -10
  14. agno/db/postgres/utils.py +0 -1
  15. agno/db/redis/redis.py +0 -4
  16. agno/db/singlestore/singlestore.py +0 -10
  17. agno/db/singlestore/utils.py +0 -1
  18. agno/db/sqlite/sqlite.py +0 -4
  19. agno/db/sqlite/utils.py +0 -1
  20. agno/integrations/discord/client.py +5 -1
  21. agno/knowledge/embedder/aws_bedrock.py +2 -2
  22. agno/models/anthropic/claude.py +2 -49
  23. agno/models/message.py +7 -6
  24. agno/os/app.py +158 -62
  25. agno/os/interfaces/agui/agui.py +1 -1
  26. agno/os/interfaces/agui/utils.py +16 -9
  27. agno/os/interfaces/slack/slack.py +2 -3
  28. agno/os/interfaces/whatsapp/whatsapp.py +2 -3
  29. agno/os/mcp.py +255 -0
  30. agno/os/router.py +33 -7
  31. agno/os/routers/evals/evals.py +9 -5
  32. agno/os/routers/knowledge/knowledge.py +30 -7
  33. agno/os/routers/memory/memory.py +17 -8
  34. agno/os/routers/metrics/metrics.py +4 -2
  35. agno/os/routers/session/session.py +8 -3
  36. agno/os/settings.py +0 -1
  37. agno/run/agent.py +87 -2
  38. agno/run/cancel.py +0 -2
  39. agno/team/team.py +2 -2
  40. agno/tools/function.py +65 -7
  41. agno/tools/linear.py +1 -1
  42. agno/utils/gemini.py +31 -1
  43. agno/utils/models/claude.py +49 -0
  44. agno/utils/streamlit.py +454 -0
  45. agno/workflow/workflow.py +8 -1
  46. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/METADATA +1 -1
  47. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/RECORD +50 -48
  48. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/WHEEL +0 -0
  49. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/licenses/LICENSE +0 -0
  50. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/top_level.txt +0 -0
agno/os/router.py CHANGED
@@ -311,7 +311,7 @@ def get_base_router(
311
311
 
312
312
  # -- Main Routes ---
313
313
 
314
- @router.get("/health", tags=["Core"])
314
+ @router.get("/health", tags=["Core"], operation_id="health_check")
315
315
  async def health_check():
316
316
  return JSONResponse(content={"status": "ok"})
317
317
 
@@ -320,6 +320,7 @@ def get_base_router(
320
320
  response_model=ConfigResponse,
321
321
  response_model_exclude_none=True,
322
322
  tags=["Core"],
323
+ operation_id="get_config",
323
324
  )
324
325
  async def config() -> ConfigResponse:
325
326
  return ConfigResponse(
@@ -347,6 +348,7 @@ def get_base_router(
347
348
  response_model=List[Model],
348
349
  response_model_exclude_none=True,
349
350
  tags=["Core"],
351
+ operation_id="get_models",
350
352
  )
351
353
  async def get_models():
352
354
  """Return the list of all models used by agents and teams in the contextual OS"""
@@ -368,7 +370,9 @@ def get_base_router(
368
370
 
369
371
  # -- Agent routes ---
370
372
 
371
- @router.post("/agents/{agent_id}/runs", tags=["Agents"])
373
+ @router.post(
374
+ "/agents/{agent_id}/runs", tags=["Agents"], operation_id="create_agent_run", response_model_exclude_none=True
375
+ )
372
376
  async def create_agent_run(
373
377
  agent_id: str,
374
378
  message: str = Form(...),
@@ -475,6 +479,8 @@ def get_base_router(
475
479
  @router.post(
476
480
  "/agents/{agent_id}/runs/{run_id}/cancel",
477
481
  tags=["Agents"],
482
+ operation_id="cancel_agent_run",
483
+ response_model_exclude_none=True,
478
484
  )
479
485
  async def cancel_agent_run(
480
486
  agent_id: str,
@@ -492,6 +498,8 @@ def get_base_router(
492
498
  @router.post(
493
499
  "/agents/{agent_id}/runs/{run_id}/continue",
494
500
  tags=["Agents"],
501
+ operation_id="continue_agent_run",
502
+ response_model_exclude_none=True,
495
503
  )
496
504
  async def continue_agent_run(
497
505
  agent_id: str,
@@ -555,6 +563,7 @@ def get_base_router(
555
563
  response_model=List[AgentResponse],
556
564
  response_model_exclude_none=True,
557
565
  tags=["Agents"],
566
+ operation_id="get_agents",
558
567
  )
559
568
  async def get_agents():
560
569
  """Return the list of all Agents present in the contextual OS"""
@@ -572,6 +581,7 @@ def get_base_router(
572
581
  response_model=AgentResponse,
573
582
  response_model_exclude_none=True,
574
583
  tags=["Agents"],
584
+ operation_id="get_agent",
575
585
  )
576
586
  async def get_agent(agent_id: str):
577
587
  agent = get_agent_by_id(agent_id, os.agents)
@@ -582,7 +592,9 @@ def get_base_router(
582
592
 
583
593
  # -- Team routes ---
584
594
 
585
- @router.post("/teams/{team_id}/runs", tags=["Teams"])
595
+ @router.post(
596
+ "/teams/{team_id}/runs", tags=["Teams"], operation_id="create_team_run", response_model_exclude_none=True
597
+ )
586
598
  async def create_team_run(
587
599
  team_id: str,
588
600
  message: str = Form(...),
@@ -686,6 +698,8 @@ def get_base_router(
686
698
  @router.post(
687
699
  "/teams/{team_id}/runs/{run_id}/cancel",
688
700
  tags=["Teams"],
701
+ operation_id="cancel_team_run",
702
+ response_model_exclude_none=True,
689
703
  )
690
704
  async def cancel_team_run(
691
705
  team_id: str,
@@ -705,6 +719,7 @@ def get_base_router(
705
719
  response_model=List[TeamResponse],
706
720
  response_model_exclude_none=True,
707
721
  tags=["Teams"],
722
+ operation_id="get_teams",
708
723
  )
709
724
  async def get_teams():
710
725
  """Return the list of all Teams present in the contextual OS"""
@@ -722,6 +737,7 @@ def get_base_router(
722
737
  response_model=TeamResponse,
723
738
  response_model_exclude_none=True,
724
739
  tags=["Teams"],
740
+ operation_id="get_team",
725
741
  )
726
742
  async def get_team(team_id: str):
727
743
  team = get_team_by_id(team_id, os.teams)
@@ -749,9 +765,10 @@ def get_base_router(
749
765
  elif action == "start-workflow":
750
766
  # Handle workflow execution directly via WebSocket
751
767
  await handle_workflow_via_websocket(websocket, message, os)
752
-
753
768
  except Exception as e:
754
- logger.error(f"WebSocket error: {e}")
769
+ if "1012" not in str(e):
770
+ logger.error(f"WebSocket error: {e}")
771
+ finally:
755
772
  # Clean up any run_ids associated with this websocket
756
773
  runs_to_remove = [run_id for run_id, ws in websocket_manager.active_connections.items() if ws == websocket]
757
774
  for run_id in runs_to_remove:
@@ -762,6 +779,7 @@ def get_base_router(
762
779
  response_model=List[WorkflowResponse],
763
780
  response_model_exclude_none=True,
764
781
  tags=["Workflows"],
782
+ operation_id="get_workflows",
765
783
  )
766
784
  async def get_workflows():
767
785
  if os.workflows is None:
@@ -774,6 +792,7 @@ def get_base_router(
774
792
  response_model=WorkflowResponse,
775
793
  response_model_exclude_none=True,
776
794
  tags=["Workflows"],
795
+ operation_id="get_workflow",
777
796
  )
778
797
  async def get_workflow(workflow_id: str):
779
798
  workflow = get_workflow_by_id(workflow_id, os.workflows)
@@ -782,7 +801,12 @@ def get_base_router(
782
801
 
783
802
  return WorkflowResponse.from_workflow(workflow)
784
803
 
785
- @router.post("/workflows/{workflow_id}/runs", tags=["Workflows"])
804
+ @router.post(
805
+ "/workflows/{workflow_id}/runs",
806
+ tags=["Workflows"],
807
+ operation_id="create_workflow_run",
808
+ response_model_exclude_none=True,
809
+ )
786
810
  async def create_workflow_run(
787
811
  workflow_id: str,
788
812
  message: str = Form(...),
@@ -828,7 +852,9 @@ def get_base_router(
828
852
  # Handle unexpected runtime errors
829
853
  raise HTTPException(status_code=500, detail=f"Error running workflow: {str(e)}")
830
854
 
831
- @router.post("/workflows/{workflow_id}/runs/{run_id}/cancel", tags=["Workflows"])
855
+ @router.post(
856
+ "/workflows/{workflow_id}/runs/{run_id}/cancel", tags=["Workflows"], operation_id="cancel_workflow_run"
857
+ )
832
858
  async def cancel_workflow_run(workflow_id: str, run_id: str):
833
859
  workflow = get_workflow_by_id(workflow_id, os.workflows)
834
860
 
@@ -37,7 +37,9 @@ def get_eval_router(
37
37
  def attach_routes(
38
38
  router: APIRouter, dbs: dict[str, BaseDb], agents: Optional[List[Agent]] = None, teams: Optional[List[Team]] = None
39
39
  ) -> APIRouter:
40
- @router.get("/eval-runs", response_model=PaginatedResponse[EvalSchema], status_code=200)
40
+ @router.get(
41
+ "/eval-runs", response_model=PaginatedResponse[EvalSchema], status_code=200, operation_id="get_eval_runs"
42
+ )
41
43
  async def get_eval_runs(
42
44
  agent_id: Optional[str] = Query(default=None, description="Agent ID"),
43
45
  team_id: Optional[str] = Query(default=None, description="Team ID"),
@@ -76,7 +78,7 @@ def attach_routes(
76
78
  ),
77
79
  )
78
80
 
79
- @router.get("/eval-runs/{eval_run_id}", response_model=EvalSchema, status_code=200)
81
+ @router.get("/eval-runs/{eval_run_id}", response_model=EvalSchema, status_code=200, operation_id="get_eval_run")
80
82
  async def get_eval_run(
81
83
  eval_run_id: str,
82
84
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
@@ -88,7 +90,7 @@ def attach_routes(
88
90
 
89
91
  return EvalSchema.from_dict(eval_run) # type: ignore
90
92
 
91
- @router.delete("/eval-runs", status_code=204)
93
+ @router.delete("/eval-runs", status_code=204, operation_id="delete_eval_runs")
92
94
  async def delete_eval_runs(
93
95
  request: DeleteEvalRunsRequest,
94
96
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
@@ -99,7 +101,9 @@ def attach_routes(
99
101
  except Exception as e:
100
102
  raise HTTPException(status_code=500, detail=f"Failed to delete eval runs: {e}")
101
103
 
102
- @router.patch("/eval-runs/{eval_run_id}", response_model=EvalSchema, status_code=200)
104
+ @router.patch(
105
+ "/eval-runs/{eval_run_id}", response_model=EvalSchema, status_code=200, operation_id="update_eval_run"
106
+ )
103
107
  async def update_eval_run(
104
108
  eval_run_id: str,
105
109
  request: UpdateEvalRunRequest,
@@ -116,7 +120,7 @@ def attach_routes(
116
120
 
117
121
  return EvalSchema.from_dict(eval_run) # type: ignore
118
122
 
119
- @router.post("/eval-runs", response_model=EvalSchema, status_code=200)
123
+ @router.post("/eval-runs", response_model=EvalSchema, status_code=200, operation_id="run_eval")
120
124
  async def run_eval(
121
125
  eval_run_input: EvalRunInput,
122
126
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
@@ -37,7 +37,9 @@ def get_knowledge_router(
37
37
 
38
38
 
39
39
  def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> APIRouter:
40
- @router.post("/knowledge/content", response_model=ContentResponseSchema, status_code=202)
40
+ @router.post(
41
+ "/knowledge/content", response_model=ContentResponseSchema, status_code=202, operation_id="upload_content"
42
+ )
41
43
  async def upload_content(
42
44
  background_tasks: BackgroundTasks,
43
45
  name: Optional[str] = Form(None),
@@ -126,7 +128,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
126
128
  )
127
129
  return response
128
130
 
129
- @router.patch("/knowledge/content/{content_id}", response_model=ContentResponseSchema, status_code=200)
131
+ @router.patch(
132
+ "/knowledge/content/{content_id}",
133
+ response_model=ContentResponseSchema,
134
+ status_code=200,
135
+ operation_id="update_content",
136
+ )
130
137
  async def update_content(
131
138
  content_id: str = Path(..., description="Content ID"),
132
139
  name: Optional[str] = Form(None, description="Content name"),
@@ -172,7 +179,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
172
179
 
173
180
  return ContentResponseSchema.from_dict(updated_content_dict)
174
181
 
175
- @router.get("/knowledge/content", response_model=PaginatedResponse[ContentResponseSchema], status_code=200)
182
+ @router.get(
183
+ "/knowledge/content",
184
+ response_model=PaginatedResponse[ContentResponseSchema],
185
+ status_code=200,
186
+ operation_id="get_content",
187
+ )
176
188
  def get_content(
177
189
  limit: Optional[int] = Query(default=20, description="Number of content entries to return"),
178
190
  page: Optional[int] = Query(default=1, description="Page number"),
@@ -209,7 +221,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
209
221
  ),
210
222
  )
211
223
 
212
- @router.get("/knowledge/content/{content_id}", response_model=ContentResponseSchema, status_code=200)
224
+ @router.get(
225
+ "/knowledge/content/{content_id}",
226
+ response_model=ContentResponseSchema,
227
+ status_code=200,
228
+ operation_id="get_content_by_id",
229
+ )
213
230
  def get_content_by_id(
214
231
  content_id: str,
215
232
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
@@ -241,6 +258,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
241
258
  response_model=ContentResponseSchema,
242
259
  status_code=200,
243
260
  response_model_exclude_none=True,
261
+ operation_id="delete_content_by_id",
244
262
  )
245
263
  def delete_content_by_id(
246
264
  content_id: str,
@@ -254,7 +272,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
254
272
  id=content_id,
255
273
  )
256
274
 
257
- @router.delete("/knowledge/content", status_code=200)
275
+ @router.delete("/knowledge/content", status_code=200, operation_id="delete_all_content")
258
276
  def delete_all_content(
259
277
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
260
278
  ):
@@ -263,7 +281,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
263
281
  knowledge.remove_all_content()
264
282
  return "success"
265
283
 
266
- @router.get("/knowledge/content/{content_id}/status", status_code=200, response_model=ContentStatusResponse)
284
+ @router.get(
285
+ "/knowledge/content/{content_id}/status",
286
+ status_code=200,
287
+ response_model=ContentStatusResponse,
288
+ operation_id="get_content_status",
289
+ )
267
290
  def get_content_status(
268
291
  content_id: str,
269
292
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
@@ -301,7 +324,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
301
324
 
302
325
  return ContentStatusResponse(status=status, status_message=status_message or "")
303
326
 
304
- @router.get("/knowledge/config", status_code=200)
327
+ @router.get("/knowledge/config", status_code=200, operation_id="get_knowledge_config")
305
328
  def get_config(
306
329
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
307
330
  ) -> ConfigResponseSchema:
@@ -28,7 +28,7 @@ def get_memory_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoAP
28
28
 
29
29
 
30
30
  def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
31
- @router.post("/memories", response_model=UserMemorySchema, status_code=200)
31
+ @router.post("/memories", response_model=UserMemorySchema, status_code=200, operation_id="create_memory")
32
32
  async def create_memory(
33
33
  payload: UserMemoryCreateSchema,
34
34
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
@@ -48,14 +48,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
48
48
 
49
49
  return UserMemorySchema.from_dict(user_memory) # type: ignore
50
50
 
51
- @router.delete("/memories/{memory_id}", status_code=204)
51
+ @router.delete("/memories/{memory_id}", status_code=204, operation_id="delete_memory")
52
52
  async def delete_memory(
53
53
  memory_id: str = Path(), db_id: Optional[str] = Query(default=None, description="The ID of the database to use")
54
54
  ) -> None:
55
55
  db = get_db(dbs, db_id)
56
56
  db.delete_user_memory(memory_id=memory_id)
57
57
 
58
- @router.delete("/memories", status_code=204)
58
+ @router.delete("/memories", status_code=204, operation_id="delete_memories")
59
59
  async def delete_memories(
60
60
  request: DeleteMemoriesRequest,
61
61
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
@@ -63,7 +63,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
63
63
  db = get_db(dbs, db_id)
64
64
  db.delete_user_memories(memory_ids=request.memory_ids)
65
65
 
66
- @router.get("/memories", response_model=PaginatedResponse[UserMemorySchema], status_code=200)
66
+ @router.get(
67
+ "/memories", response_model=PaginatedResponse[UserMemorySchema], status_code=200, operation_id="get_memories"
68
+ )
67
69
  async def get_memories(
68
70
  user_id: Optional[str] = Query(default=None, description="Filter memories by user ID"),
69
71
  agent_id: Optional[str] = Query(default=None, description="Filter memories by agent ID"),
@@ -99,7 +101,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
99
101
  ),
100
102
  )
101
103
 
102
- @router.get("/memories/{memory_id}", response_model=UserMemorySchema, status_code=200)
104
+ @router.get("/memories/{memory_id}", response_model=UserMemorySchema, status_code=200, operation_id="get_memory")
103
105
  async def get_memory(
104
106
  memory_id: str = Path(),
105
107
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
@@ -111,14 +113,16 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
111
113
 
112
114
  return UserMemorySchema.from_dict(user_memory) # type: ignore
113
115
 
114
- @router.get("/memories/topics", response_model=List[str], status_code=200)
116
+ @router.get("/memories/topics", response_model=List[str], status_code=200, operation_id="get_memory_topics")
115
117
  async def get_topics(
116
118
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
117
119
  ) -> List[str]:
118
120
  db = get_db(dbs, db_id)
119
121
  return db.get_all_memory_topics()
120
122
 
121
- @router.patch("/memories/{memory_id}", response_model=UserMemorySchema, status_code=200)
123
+ @router.patch(
124
+ "/memories/{memory_id}", response_model=UserMemorySchema, status_code=200, operation_id="update_memory"
125
+ )
122
126
  async def update_memory(
123
127
  payload: UserMemoryCreateSchema,
124
128
  memory_id: str = Path(),
@@ -139,7 +143,12 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
139
143
 
140
144
  return UserMemorySchema.from_dict(user_memory) # type: ignore
141
145
 
142
- @router.get("/user_memory_stats", response_model=PaginatedResponse[UserStatsSchema], status_code=200)
146
+ @router.get(
147
+ "/user_memory_stats",
148
+ response_model=PaginatedResponse[UserStatsSchema],
149
+ status_code=200,
150
+ operation_id="get_user_memory_stats",
151
+ )
143
152
  async def get_user_memory_stats(
144
153
  limit: Optional[int] = Query(default=20, description="Number of items to return"),
145
154
  page: Optional[int] = Query(default=1, description="Page number"),
@@ -20,7 +20,7 @@ def get_metrics_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoA
20
20
 
21
21
 
22
22
  def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
23
- @router.get("/metrics", response_model=MetricsResponse, status_code=200)
23
+ @router.get("/metrics", response_model=MetricsResponse, status_code=200, operation_id="get_metrics")
24
24
  async def get_metrics(
25
25
  starting_date: Optional[date] = Query(default=None, description="Starting date to filter metrics (YYYY-MM-DD)"),
26
26
  ending_date: Optional[date] = Query(default=None, description="Ending date to filter metrics (YYYY-MM-DD)"),
@@ -40,7 +40,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
40
40
  except Exception as e:
41
41
  raise HTTPException(status_code=500, detail=f"Error getting metrics: {str(e)}")
42
42
 
43
- @router.post("/metrics/refresh", response_model=List[DayAggregatedMetrics], status_code=200)
43
+ @router.post(
44
+ "/metrics/refresh", response_model=List[DayAggregatedMetrics], status_code=200, operation_id="refresh_metrics"
45
+ )
44
46
  async def calculate_metrics(
45
47
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
46
48
  ) -> List[DayAggregatedMetrics]:
@@ -30,7 +30,9 @@ def get_session_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoA
30
30
 
31
31
 
32
32
  def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
33
- @router.get("/sessions", response_model=PaginatedResponse[SessionSchema], status_code=200)
33
+ @router.get(
34
+ "/sessions", response_model=PaginatedResponse[SessionSchema], status_code=200, operation_id="get_sessions"
35
+ )
34
36
  async def get_sessions(
35
37
  session_type: SessionType = Query(default=SessionType.AGENT, alias="type"),
36
38
  component_id: Optional[str] = Query(default=None, description="Filter sessions by component ID"),
@@ -69,6 +71,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
69
71
  "/sessions/{session_id}",
70
72
  response_model=Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema],
71
73
  status_code=200,
74
+ operation_id="get_session_by_id",
72
75
  )
73
76
  async def get_session_by_id(
74
77
  session_id: str = Path(...),
@@ -91,6 +94,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
91
94
  "/sessions/{session_id}/runs",
92
95
  response_model=Union[List[RunSchema], List[TeamRunSchema], List[WorkflowRunSchema]],
93
96
  status_code=200,
97
+ operation_id="get_session_runs",
94
98
  )
95
99
  async def get_session_runs(
96
100
  session_id: str = Path(..., description="Session ID", alias="session_id"),
@@ -118,7 +122,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
118
122
  else:
119
123
  return [RunSchema.from_dict(run) for run in runs]
120
124
 
121
- @router.delete("/sessions/{session_id}", status_code=204)
125
+ @router.delete("/sessions/{session_id}", status_code=204, operation_id="delete_session")
122
126
  async def delete_session(
123
127
  session_id: str = Path(...),
124
128
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
@@ -126,7 +130,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
126
130
  db = get_db(dbs, db_id)
127
131
  db.delete_session(session_id=session_id)
128
132
 
129
- @router.delete("/sessions", status_code=204)
133
+ @router.delete("/sessions", status_code=204, operation_id="delete_sessions")
130
134
  async def delete_sessions(
131
135
  request: DeleteSessionRequest,
132
136
  session_type: SessionType = Query(default=SessionType.AGENT, description="Session type filter", alias="type"),
@@ -141,6 +145,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
141
145
  @router.post(
142
146
  "/sessions/{session_id}/rename",
143
147
  response_model=Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema],
148
+ operation_id="rename_session",
144
149
  )
145
150
  async def rename_session(
146
151
  session_id: str = Path(...),
agno/os/settings.py CHANGED
@@ -13,7 +13,6 @@ class AgnoAPISettings(BaseSettings):
13
13
  """
14
14
 
15
15
  env: str = "dev"
16
- title: str = "Agno AgentOS"
17
16
 
18
17
  # Set to False to disable docs server at /docs and /redoc
19
18
  docs_enabled: bool = True
agno/run/agent.py CHANGED
@@ -1,11 +1,11 @@
1
1
  from dataclasses import asdict, dataclass, field
2
2
  from enum import Enum
3
3
  from time import time
4
- from typing import Any, Dict, List, Optional, Union
4
+ from typing import Any, Dict, List, Optional, Sequence, Union
5
5
 
6
6
  from pydantic import BaseModel
7
7
 
8
- from agno.media import AudioArtifact, AudioResponse, ImageArtifact, VideoArtifact
8
+ from agno.media import AudioArtifact, AudioResponse, File, ImageArtifact, VideoArtifact
9
9
  from agno.models.message import Citations, Message
10
10
  from agno.models.metrics import Metrics
11
11
  from agno.models.response import ToolExecution
@@ -281,6 +281,78 @@ def run_output_event_from_dict(data: dict) -> BaseRunOutputEvent:
281
281
  return cls.from_dict(data) # type: ignore
282
282
 
283
283
 
284
+ @dataclass
285
+ class RunInput:
286
+ """Container for the raw input data passed to Agent.run().
287
+
288
+ This captures the original input exactly as provided by the user,
289
+ separate from the processed messages that go to the model.
290
+
291
+ Attributes:
292
+ input_content: The literal input message/content passed to run()
293
+ images: Images directly passed to run()
294
+ videos: Videos directly passed to run()
295
+ audios: Audio files directly passed to run()
296
+ files: Files directly passed to run()
297
+ """
298
+
299
+ input_content: Optional[Union[str, List, Dict, Message, BaseModel, List[Message]]] = None
300
+ images: Optional[Sequence[ImageArtifact]] = None
301
+ videos: Optional[Sequence[VideoArtifact]] = None
302
+ audios: Optional[Sequence[AudioArtifact]] = None
303
+ files: Optional[Sequence[File]] = None
304
+
305
+ def to_dict(self) -> Dict[str, Any]:
306
+ """Convert to dictionary representation"""
307
+ result: Dict[str, Any] = {}
308
+
309
+ if self.input_content is not None:
310
+ if isinstance(self.input_content, (str)):
311
+ result["input_content"] = self.input_content
312
+ elif isinstance(self.input_content, BaseModel):
313
+ result["input_content"] = self.input_content.model_dump(exclude_none=True)
314
+ elif isinstance(self.input_content, Message):
315
+ result["input_content"] = self.input_content.to_dict()
316
+ elif (
317
+ isinstance(self.input_content, list)
318
+ and self.input_content
319
+ and isinstance(self.input_content[0], Message)
320
+ ):
321
+ result["input_content"] = [m.to_dict() for m in self.input_content]
322
+ else:
323
+ result["input_content"] = self.input_content
324
+
325
+ if self.images:
326
+ result["images"] = [img.to_dict() for img in self.images]
327
+ if self.videos:
328
+ result["videos"] = [vid.to_dict() for vid in self.videos]
329
+ if self.audios:
330
+ result["audios"] = [aud.to_dict() for aud in self.audios]
331
+
332
+ return result
333
+
334
+ @classmethod
335
+ def from_dict(cls, data: Dict[str, Any]) -> "RunInput":
336
+ """Create RunInput from dictionary"""
337
+ images = None
338
+ if data.get("images"):
339
+ images = [ImageArtifact.model_validate(img_data) for img_data in data["images"]]
340
+
341
+ videos = None
342
+ if data.get("videos"):
343
+ videos = [VideoArtifact.model_validate(vid_data) for vid_data in data["videos"]]
344
+
345
+ audios = None
346
+ if data.get("audios"):
347
+ audios = [AudioArtifact.model_validate(aud_data) for aud_data in data["audios"]]
348
+
349
+ files = None
350
+ if data.get("files"):
351
+ files = [File.model_validate(file_data) for file_data in data["files"]]
352
+
353
+ return cls(input_content=data.get("input_content"), images=images, videos=videos, audios=audios, files=files)
354
+
355
+
284
356
  @dataclass
285
357
  class RunOutput:
286
358
  """Response returned by Agent.run() or Workflow.run() functions"""
@@ -313,6 +385,9 @@ class RunOutput:
313
385
  audio: Optional[List[AudioArtifact]] = None # Audio attached to the response
314
386
  response_audio: Optional[AudioResponse] = None # Model audio response
315
387
 
388
+ # Input media and messages from user
389
+ input: Optional[RunInput] = None
390
+
316
391
  citations: Optional[Citations] = None
317
392
  references: Optional[List[MessageReferences]] = None
318
393
 
@@ -363,6 +438,7 @@ class RunOutput:
363
438
  "videos",
364
439
  "audio",
365
440
  "response_audio",
441
+ "input",
366
442
  "citations",
367
443
  "events",
368
444
  "additional_input",
@@ -446,6 +522,9 @@ class RunOutput:
446
522
  else:
447
523
  _dict["tools"].append(tool)
448
524
 
525
+ if self.input is not None:
526
+ _dict["input"] = self.input.to_dict()
527
+
449
528
  return _dict
450
529
 
451
530
  def to_json(self) -> str:
@@ -488,6 +567,11 @@ class RunOutput:
488
567
  response_audio = data.pop("response_audio", None)
489
568
  response_audio = AudioResponse.model_validate(response_audio) if response_audio else None
490
569
 
570
+ input_data = data.pop("input", None)
571
+ input_obj = None
572
+ if input_data:
573
+ input_obj = RunInput.from_dict(input_data)
574
+
491
575
  metrics = data.pop("metrics", None)
492
576
  if metrics:
493
577
  metrics = Metrics(**metrics)
@@ -518,6 +602,7 @@ class RunOutput:
518
602
  audio=audio,
519
603
  videos=videos,
520
604
  response_audio=response_audio,
605
+ input=input_obj,
521
606
  events=events,
522
607
  additional_input=additional_input,
523
608
  reasoning_steps=reasoning_steps,
agno/run/cancel.py CHANGED
@@ -18,7 +18,6 @@ class RunCancellationManager:
18
18
  """Register a new run as not cancelled."""
19
19
  with self._lock:
20
20
  self._cancelled_runs[run_id] = False
21
- logger.debug(f"Registered run {run_id} for cancellation tracking")
22
21
 
23
22
  def cancel_run(self, run_id: str) -> bool:
24
23
  """Cancel a run by marking it as cancelled.
@@ -45,7 +44,6 @@ class RunCancellationManager:
45
44
  with self._lock:
46
45
  if run_id in self._cancelled_runs:
47
46
  del self._cancelled_runs[run_id]
48
- logger.debug(f"Cleaned up cancellation tracking for run {run_id}")
49
47
 
50
48
  def raise_if_cancelled(self, run_id: str) -> None:
51
49
  """Check if a run should be cancelled and raise exception if so."""
agno/team/team.py CHANGED
@@ -25,7 +25,7 @@ from typing import (
25
25
  get_args,
26
26
  overload,
27
27
  )
28
- from uuid import NAMESPACE_DNS, uuid4, uuid5
28
+ from uuid import uuid4
29
29
 
30
30
  from pydantic import BaseModel
31
31
 
@@ -563,7 +563,7 @@ class Team:
563
563
  """
564
564
  if self.id is None:
565
565
  if self.name is not None:
566
- self.id = str(uuid5(NAMESPACE_DNS, self.name))
566
+ self.id = self.name.lower().replace(" ", "-")
567
567
  else:
568
568
  self.id = str(uuid4())
569
569