agno 2.1.9__py3-none-any.whl → 2.2.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 (83) hide show
  1. agno/agent/agent.py +2048 -1204
  2. agno/culture/__init__.py +3 -0
  3. agno/culture/manager.py +954 -0
  4. agno/db/async_postgres/async_postgres.py +232 -0
  5. agno/db/async_postgres/schemas.py +15 -0
  6. agno/db/async_postgres/utils.py +58 -0
  7. agno/db/base.py +83 -6
  8. agno/db/dynamo/dynamo.py +162 -0
  9. agno/db/dynamo/schemas.py +44 -0
  10. agno/db/dynamo/utils.py +59 -0
  11. agno/db/firestore/firestore.py +231 -0
  12. agno/db/firestore/schemas.py +10 -0
  13. agno/db/firestore/utils.py +96 -0
  14. agno/db/gcs_json/gcs_json_db.py +190 -0
  15. agno/db/gcs_json/utils.py +58 -0
  16. agno/db/in_memory/in_memory_db.py +118 -0
  17. agno/db/in_memory/utils.py +58 -0
  18. agno/db/json/json_db.py +129 -0
  19. agno/db/json/utils.py +58 -0
  20. agno/db/mongo/mongo.py +222 -0
  21. agno/db/mongo/schemas.py +10 -0
  22. agno/db/mongo/utils.py +59 -0
  23. agno/db/mysql/mysql.py +232 -1
  24. agno/db/mysql/schemas.py +14 -0
  25. agno/db/mysql/utils.py +58 -0
  26. agno/db/postgres/postgres.py +242 -0
  27. agno/db/postgres/schemas.py +15 -0
  28. agno/db/postgres/utils.py +58 -0
  29. agno/db/redis/redis.py +181 -0
  30. agno/db/redis/schemas.py +14 -0
  31. agno/db/redis/utils.py +58 -0
  32. agno/db/schemas/__init__.py +2 -1
  33. agno/db/schemas/culture.py +120 -0
  34. agno/db/singlestore/schemas.py +14 -0
  35. agno/db/singlestore/singlestore.py +231 -0
  36. agno/db/singlestore/utils.py +58 -0
  37. agno/db/sqlite/schemas.py +14 -0
  38. agno/db/sqlite/sqlite.py +274 -7
  39. agno/db/sqlite/utils.py +62 -0
  40. agno/db/surrealdb/models.py +51 -1
  41. agno/db/surrealdb/surrealdb.py +154 -0
  42. agno/db/surrealdb/utils.py +61 -1
  43. agno/knowledge/reader/field_labeled_csv_reader.py +0 -2
  44. agno/memory/manager.py +28 -11
  45. agno/models/anthropic/claude.py +2 -2
  46. agno/models/message.py +0 -1
  47. agno/models/ollama/chat.py +7 -2
  48. agno/os/app.py +29 -7
  49. agno/os/interfaces/a2a/router.py +2 -2
  50. agno/os/interfaces/agui/router.py +2 -2
  51. agno/os/router.py +7 -7
  52. agno/os/routers/evals/schemas.py +31 -31
  53. agno/os/routers/health.py +6 -2
  54. agno/os/routers/knowledge/schemas.py +49 -47
  55. agno/os/routers/memory/schemas.py +16 -16
  56. agno/os/routers/metrics/schemas.py +16 -16
  57. agno/os/routers/session/session.py +382 -7
  58. agno/os/schema.py +254 -231
  59. agno/os/utils.py +1 -1
  60. agno/run/agent.py +49 -1
  61. agno/run/team.py +43 -0
  62. agno/session/summary.py +45 -13
  63. agno/session/team.py +90 -5
  64. agno/team/team.py +1118 -857
  65. agno/tools/gmail.py +59 -14
  66. agno/utils/agent.py +372 -0
  67. agno/utils/events.py +144 -2
  68. agno/utils/print_response/agent.py +10 -6
  69. agno/utils/print_response/team.py +6 -4
  70. agno/utils/print_response/workflow.py +7 -5
  71. agno/utils/team.py +9 -8
  72. agno/workflow/condition.py +17 -9
  73. agno/workflow/loop.py +18 -10
  74. agno/workflow/parallel.py +14 -6
  75. agno/workflow/router.py +17 -9
  76. agno/workflow/step.py +14 -6
  77. agno/workflow/steps.py +14 -6
  78. agno/workflow/workflow.py +245 -122
  79. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/METADATA +60 -23
  80. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/RECORD +83 -79
  81. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/WHEEL +0 -0
  82. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/licenses/LICENSE +0 -0
  83. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/top_level.txt +0 -0
agno/tools/gmail.py CHANGED
@@ -124,20 +124,6 @@ class GmailTools(Toolkit):
124
124
  self.scopes = scopes or self.DEFAULT_SCOPES
125
125
  self.port = port
126
126
 
127
- """ tools functions:
128
- enable_get_latest_emails (bool): Enable getting latest emails.
129
- enable_get_emails_from_user (bool): Enable getting emails from specific user.
130
- enable_get_unread_emails (bool): Enable getting unread emails.
131
- enable_get_starred_emails (bool): Enable getting starred emails.
132
- enable_get_emails_by_context (bool): Enable getting emails by context.
133
- enable_get_emails_by_date (bool): Enable getting emails by date.
134
- enable_get_emails_by_thread (bool): Enable getting emails by thread.
135
- enable_create_draft_email (bool): Enable creating draft emails.
136
- enable_send_email (bool): Enable sending emails.
137
- enable_send_email_reply (bool): Enable sending email replies.
138
- all (bool): Enable all tools.
139
- """
140
-
141
127
  tools: List[Any] = [
142
128
  # Reading emails
143
129
  self.get_latest_emails,
@@ -148,6 +134,9 @@ class GmailTools(Toolkit):
148
134
  self.get_emails_by_date,
149
135
  self.get_emails_by_thread,
150
136
  self.search_emails,
137
+ # Email management
138
+ self.mark_email_as_read,
139
+ self.mark_email_as_unread,
151
140
  # Composing emails
152
141
  self.create_draft_email,
153
142
  self.send_email,
@@ -173,12 +162,18 @@ class GmailTools(Toolkit):
173
162
  "get_emails_by_thread",
174
163
  "search_emails",
175
164
  ]
165
+ modify_operations = ["mark_email_as_read", "mark_email_as_unread"]
176
166
  if any(read_operation in self.functions for read_operation in read_operations):
177
167
  read_scope = "https://www.googleapis.com/auth/gmail.readonly"
178
168
  write_scope = "https://www.googleapis.com/auth/gmail.modify"
179
169
  if read_scope not in self.scopes and write_scope not in self.scopes:
180
170
  raise ValueError(f"The scope {read_scope} is required for email reading operations")
181
171
 
172
+ if any(modify_operation in self.functions for modify_operation in modify_operations):
173
+ modify_scope = "https://www.googleapis.com/auth/gmail.modify"
174
+ if modify_scope not in self.scopes:
175
+ raise ValueError(f"The scope {modify_scope} is required for email modification operations")
176
+
182
177
  def _auth(self) -> None:
183
178
  """Authenticate with Gmail API"""
184
179
  token_file = Path(self.token_path or "token.json")
@@ -555,6 +550,56 @@ class GmailTools(Toolkit):
555
550
  except Exception as error:
556
551
  return f"Unexpected error retrieving emails with query '{query}': {type(error).__name__}: {error}"
557
552
 
553
+ @authenticate
554
+ def mark_email_as_read(self, message_id: str) -> str:
555
+ """
556
+ Mark a specific email as read by removing the 'UNREAD' label.
557
+ This is crucial for long polling scenarios to prevent processing the same email multiple times.
558
+
559
+ Args:
560
+ message_id (str): The ID of the message to mark as read
561
+
562
+ Returns:
563
+ str: Success message or error description
564
+ """
565
+ try:
566
+ # Remove the UNREAD label to mark the email as read
567
+ modify_request = {"removeLabelIds": ["UNREAD"]}
568
+
569
+ self.service.users().messages().modify(userId="me", id=message_id, body=modify_request).execute() # type: ignore
570
+
571
+ return f"Successfully marked email {message_id} as read. Labels removed: UNREAD"
572
+
573
+ except HttpError as error:
574
+ return f"HTTP Error marking email {message_id} as read: {error}"
575
+ except Exception as error:
576
+ return f"Error marking email {message_id} as read: {type(error).__name__}: {error}"
577
+
578
+ @authenticate
579
+ def mark_email_as_unread(self, message_id: str) -> str:
580
+ """
581
+ Mark a specific email as unread by adding the 'UNREAD' label.
582
+ This is useful for flagging emails that need attention or re-processing.
583
+
584
+ Args:
585
+ message_id (str): The ID of the message to mark as unread
586
+
587
+ Returns:
588
+ str: Success message or error description
589
+ """
590
+ try:
591
+ # Add the UNREAD label to mark the email as unread
592
+ modify_request = {"addLabelIds": ["UNREAD"]}
593
+
594
+ self.service.users().messages().modify(userId="me", id=message_id, body=modify_request).execute() # type: ignore
595
+
596
+ return f"Successfully marked email {message_id} as unread. Labels added: UNREAD"
597
+
598
+ except HttpError as error:
599
+ return f"HTTP Error marking email {message_id} as unread: {error}"
600
+ except Exception as error:
601
+ return f"Error marking email {message_id} as unread: {type(error).__name__}: {error}"
602
+
558
603
  def _validate_email_params(self, to: str, subject: str, body: str) -> None:
559
604
  """Validate email parameters."""
560
605
  if not to:
agno/utils/agent.py ADDED
@@ -0,0 +1,372 @@
1
+ from asyncio import Future, Task
2
+ from typing import AsyncIterator, Iterator, List, Optional, Sequence, Union
3
+
4
+ from agno.media import Audio, File, Image, Video
5
+ from agno.models.message import Message
6
+ from agno.run.agent import RunEvent, RunInput, RunOutput, RunOutputEvent
7
+ from agno.run.team import RunOutputEvent as TeamRunOutputEvent
8
+ from agno.run.team import TeamRunOutput
9
+ from agno.session import AgentSession, TeamSession
10
+ from agno.utils.events import (
11
+ create_memory_update_completed_event,
12
+ create_memory_update_started_event,
13
+ create_team_memory_update_completed_event,
14
+ create_team_memory_update_started_event,
15
+ handle_event,
16
+ )
17
+ from agno.utils.log import log_debug, log_warning
18
+
19
+
20
+ async def await_for_background_tasks(
21
+ memory_task: Optional[Task] = None,
22
+ cultural_knowledge_task: Optional[Task] = None,
23
+ ) -> None:
24
+ if memory_task is not None:
25
+ try:
26
+ await memory_task
27
+ except Exception as e:
28
+ log_warning(f"Error in memory creation: {str(e)}")
29
+
30
+ if cultural_knowledge_task is not None:
31
+ try:
32
+ await cultural_knowledge_task
33
+ except Exception as e:
34
+ log_warning(f"Error in cultural knowledge creation: {str(e)}")
35
+
36
+
37
+ def wait_for_background_tasks(
38
+ memory_future: Optional[Future] = None, cultural_knowledge_future: Optional[Future] = None
39
+ ) -> None:
40
+ if memory_future is not None:
41
+ try:
42
+ memory_future.result()
43
+ except Exception as e:
44
+ log_warning(f"Error in memory creation: {str(e)}")
45
+
46
+ # Wait for cultural knowledge creation
47
+ if cultural_knowledge_future is not None:
48
+ try:
49
+ cultural_knowledge_future.result()
50
+ except Exception as e:
51
+ log_warning(f"Error in cultural knowledge creation: {str(e)}")
52
+
53
+
54
+ async def await_for_background_tasks_stream(
55
+ run_response: Union[RunOutput, TeamRunOutput],
56
+ memory_task: Optional[Task] = None,
57
+ cultural_knowledge_task: Optional[Task] = None,
58
+ stream_events: bool = False,
59
+ events_to_skip: Optional[List[RunEvent]] = None,
60
+ store_events: bool = False,
61
+ ) -> AsyncIterator[RunOutputEvent]:
62
+ if memory_task is not None:
63
+ if stream_events:
64
+ if isinstance(run_response, TeamRunOutput):
65
+ yield handle_event( # type: ignore
66
+ create_team_memory_update_started_event(from_run_response=run_response),
67
+ run_response,
68
+ events_to_skip=events_to_skip, # type: ignore
69
+ store_events=store_events,
70
+ )
71
+ else:
72
+ yield handle_event( # type: ignore
73
+ create_memory_update_started_event(from_run_response=run_response),
74
+ run_response,
75
+ events_to_skip=events_to_skip, # type: ignore
76
+ store_events=store_events,
77
+ )
78
+ try:
79
+ await memory_task
80
+ except Exception as e:
81
+ log_warning(f"Error in memory creation: {str(e)}")
82
+ if stream_events:
83
+ if isinstance(run_response, TeamRunOutput):
84
+ yield handle_event( # type: ignore
85
+ create_team_memory_update_completed_event(from_run_response=run_response),
86
+ run_response,
87
+ events_to_skip=events_to_skip, # type: ignore
88
+ store_events=store_events,
89
+ )
90
+ else:
91
+ yield handle_event( # type: ignore
92
+ create_memory_update_completed_event(from_run_response=run_response),
93
+ run_response,
94
+ events_to_skip=events_to_skip, # type: ignore
95
+ store_events=store_events,
96
+ )
97
+
98
+ if cultural_knowledge_task is not None:
99
+ try:
100
+ await cultural_knowledge_task
101
+ except Exception as e:
102
+ log_warning(f"Error in cultural knowledge creation: {str(e)}")
103
+
104
+
105
+ def wait_for_background_tasks_stream(
106
+ run_response: Union[TeamRunOutput, RunOutput],
107
+ memory_future: Optional[Future] = None,
108
+ cultural_knowledge_future: Optional[Future] = None,
109
+ stream_events: bool = False,
110
+ events_to_skip: Optional[List[RunEvent]] = None,
111
+ store_events: bool = False,
112
+ ) -> Iterator[Union[RunOutputEvent, TeamRunOutputEvent]]:
113
+ if memory_future is not None:
114
+ if stream_events:
115
+ if isinstance(run_response, TeamRunOutput):
116
+ yield handle_event( # type: ignore
117
+ create_team_memory_update_started_event(from_run_response=run_response),
118
+ run_response,
119
+ events_to_skip=events_to_skip, # type: ignore
120
+ store_events=store_events,
121
+ )
122
+ else:
123
+ yield handle_event( # type: ignore
124
+ create_memory_update_started_event(from_run_response=run_response),
125
+ run_response,
126
+ events_to_skip=events_to_skip, # type: ignore
127
+ store_events=store_events,
128
+ )
129
+ try:
130
+ memory_future.result()
131
+ except Exception as e:
132
+ log_warning(f"Error in memory creation: {str(e)}")
133
+ if stream_events:
134
+ if isinstance(run_response, TeamRunOutput):
135
+ yield handle_event( # type: ignore
136
+ create_team_memory_update_completed_event(from_run_response=run_response),
137
+ run_response,
138
+ events_to_skip=events_to_skip, # type: ignore
139
+ store_events=store_events,
140
+ )
141
+ else:
142
+ yield handle_event( # type: ignore
143
+ create_memory_update_completed_event(from_run_response=run_response),
144
+ run_response,
145
+ events_to_skip=events_to_skip, # type: ignore
146
+ store_events=store_events,
147
+ )
148
+
149
+ # Wait for cultural knowledge creation
150
+ if cultural_knowledge_future is not None:
151
+ # TODO: Add events
152
+ try:
153
+ cultural_knowledge_future.result()
154
+ except Exception as e:
155
+ log_warning(f"Error in cultural knowledge creation: {str(e)}")
156
+
157
+
158
+ def collect_joint_images(
159
+ run_input: Optional[RunInput] = None,
160
+ session: Optional[Union[AgentSession, TeamSession]] = None,
161
+ ) -> Optional[Sequence[Image]]:
162
+ """Collect images from input, session history, and current run response."""
163
+ joint_images: List[Image] = []
164
+
165
+ # 1. Add images from current input
166
+ if run_input and run_input.images:
167
+ joint_images.extend(run_input.images)
168
+ log_debug(f"Added {len(run_input.images)} input images to joint list")
169
+
170
+ # 2. Add images from session history (from both input and generated sources)
171
+ try:
172
+ if session and session.runs:
173
+ for historical_run in session.runs:
174
+ # Add generated images from previous runs
175
+ if historical_run.images:
176
+ joint_images.extend(historical_run.images)
177
+ log_debug(
178
+ f"Added {len(historical_run.images)} generated images from historical run {historical_run.run_id}"
179
+ )
180
+
181
+ # Add input images from previous runs
182
+ if historical_run.input and historical_run.input.images:
183
+ joint_images.extend(historical_run.input.images)
184
+ log_debug(
185
+ f"Added {len(historical_run.input.images)} input images from historical run {historical_run.run_id}"
186
+ )
187
+ except Exception as e:
188
+ log_debug(f"Could not access session history for images: {e}")
189
+
190
+ if joint_images:
191
+ log_debug(f"Images Available to Model: {len(joint_images)} images")
192
+ return joint_images if joint_images else None
193
+
194
+
195
+ def collect_joint_videos(
196
+ run_input: Optional[RunInput] = None,
197
+ session: Optional[Union[AgentSession, TeamSession]] = None,
198
+ ) -> Optional[Sequence[Video]]:
199
+ """Collect videos from input, session history, and current run response."""
200
+ joint_videos: List[Video] = []
201
+
202
+ # 1. Add videos from current input
203
+ if run_input and run_input.videos:
204
+ joint_videos.extend(run_input.videos)
205
+ log_debug(f"Added {len(run_input.videos)} input videos to joint list")
206
+
207
+ # 2. Add videos from session history (from both input and generated sources)
208
+ try:
209
+ if session and session.runs:
210
+ for historical_run in session.runs:
211
+ # Add generated videos from previous runs
212
+ if historical_run.videos:
213
+ joint_videos.extend(historical_run.videos)
214
+ log_debug(
215
+ f"Added {len(historical_run.videos)} generated videos from historical run {historical_run.run_id}"
216
+ )
217
+
218
+ # Add input videos from previous runs
219
+ if historical_run.input and historical_run.input.videos:
220
+ joint_videos.extend(historical_run.input.videos)
221
+ log_debug(
222
+ f"Added {len(historical_run.input.videos)} input videos from historical run {historical_run.run_id}"
223
+ )
224
+ except Exception as e:
225
+ log_debug(f"Could not access session history for videos: {e}")
226
+
227
+ if joint_videos:
228
+ log_debug(f"Videos Available to Model: {len(joint_videos)} videos")
229
+ return joint_videos if joint_videos else None
230
+
231
+
232
+ def collect_joint_audios(
233
+ run_input: Optional[RunInput] = None,
234
+ session: Optional[Union[AgentSession, TeamSession]] = None,
235
+ ) -> Optional[Sequence[Audio]]:
236
+ """Collect audios from input, session history, and current run response."""
237
+ joint_audios: List[Audio] = []
238
+
239
+ # 1. Add audios from current input
240
+ if run_input and run_input.audios:
241
+ joint_audios.extend(run_input.audios)
242
+ log_debug(f"Added {len(run_input.audios)} input audios to joint list")
243
+
244
+ # 2. Add audios from session history (from both input and generated sources)
245
+ try:
246
+ if session and session.runs:
247
+ for historical_run in session.runs:
248
+ # Add generated audios from previous runs
249
+ if historical_run.audio:
250
+ joint_audios.extend(historical_run.audio)
251
+ log_debug(
252
+ f"Added {len(historical_run.audio)} generated audios from historical run {historical_run.run_id}"
253
+ )
254
+
255
+ # Add input audios from previous runs
256
+ if historical_run.input and historical_run.input.audios:
257
+ joint_audios.extend(historical_run.input.audios)
258
+ log_debug(
259
+ f"Added {len(historical_run.input.audios)} input audios from historical run {historical_run.run_id}"
260
+ )
261
+ except Exception as e:
262
+ log_debug(f"Could not access session history for audios: {e}")
263
+
264
+ if joint_audios:
265
+ log_debug(f"Audios Available to Model: {len(joint_audios)} audios")
266
+ return joint_audios if joint_audios else None
267
+
268
+
269
+ def collect_joint_files(
270
+ run_input: Optional[RunInput] = None,
271
+ ) -> Optional[Sequence[File]]:
272
+ """Collect files from input and session history."""
273
+ from agno.utils.log import log_debug
274
+
275
+ joint_files: List[File] = []
276
+
277
+ # 1. Add files from current input
278
+ if run_input and run_input.files:
279
+ joint_files.extend(run_input.files)
280
+
281
+ # TODO: Files aren't stored in session history yet and dont have a FileArtifact
282
+
283
+ if joint_files:
284
+ log_debug(f"Files Available to Model: {len(joint_files)} files")
285
+
286
+ return joint_files if joint_files else None
287
+
288
+
289
+ def scrub_media_from_run_output(run_response: Union[RunOutput, TeamRunOutput]) -> None:
290
+ """
291
+ Completely remove all media from RunOutput when store_media=False.
292
+ This includes media in input, output artifacts, and all messages.
293
+ """
294
+ # 1. Scrub RunInput media
295
+ if run_response.input is not None:
296
+ run_response.input.images = []
297
+ run_response.input.videos = []
298
+ run_response.input.audios = []
299
+ run_response.input.files = []
300
+
301
+ # 3. Scrub media from all messages
302
+ if run_response.messages:
303
+ for message in run_response.messages:
304
+ scrub_media_from_message(message)
305
+
306
+ # 4. Scrub media from additional_input messages if any
307
+ if run_response.additional_input:
308
+ for message in run_response.additional_input:
309
+ scrub_media_from_message(message)
310
+
311
+ # 5. Scrub media from reasoning_messages if any
312
+ if run_response.reasoning_messages:
313
+ for message in run_response.reasoning_messages:
314
+ scrub_media_from_message(message)
315
+
316
+
317
+ def scrub_media_from_message(message: Message) -> None:
318
+ """Remove all media from a Message object."""
319
+ # Input media
320
+ message.images = None
321
+ message.videos = None
322
+ message.audio = None
323
+ message.files = None
324
+
325
+ # Output media
326
+ message.audio_output = None
327
+ message.image_output = None
328
+ message.video_output = None
329
+
330
+
331
+ def scrub_tool_results_from_run_output(run_response: Union[RunOutput, TeamRunOutput]) -> None:
332
+ """
333
+ Remove all tool-related data from RunOutput when store_tool_messages=False.
334
+ This removes both the tool call and its corresponding result to maintain API consistency.
335
+ """
336
+ if not run_response.messages:
337
+ return
338
+
339
+ # Step 1: Collect all tool_call_ids from tool result messages
340
+ tool_call_ids_to_remove = set()
341
+ for message in run_response.messages:
342
+ if message.role == "tool" and message.tool_call_id:
343
+ tool_call_ids_to_remove.add(message.tool_call_id)
344
+
345
+ # Step 2: Remove tool result messages (role="tool")
346
+ run_response.messages = [msg for msg in run_response.messages if msg.role != "tool"]
347
+
348
+ # Step 3: Remove assistant messages that made those tool calls
349
+ filtered_messages = []
350
+ for message in run_response.messages:
351
+ # Check if this assistant message made any of the tool calls we're removing
352
+ should_remove = False
353
+ if message.role == "assistant" and message.tool_calls:
354
+ for tool_call in message.tool_calls:
355
+ if tool_call.get("id") in tool_call_ids_to_remove:
356
+ should_remove = True
357
+ break
358
+
359
+ if not should_remove:
360
+ filtered_messages.append(message)
361
+
362
+ run_response.messages = filtered_messages
363
+
364
+
365
+ def scrub_history_messages_from_run_output(run_response: Union[RunOutput, TeamRunOutput]) -> None:
366
+ """
367
+ Remove all history messages from TeamRunOutput when store_history_messages=False.
368
+ This removes messages that were loaded from the team's memory.
369
+ """
370
+ # Remove messages with from_history=True
371
+ if run_response.messages:
372
+ run_response.messages = [msg for msg in run_response.messages if not msg.from_history]
agno/utils/events.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict, List, Optional
1
+ from typing import Any, Dict, List, Optional, Union
2
2
 
3
3
  from agno.media import Audio, Image
4
4
  from agno.models.message import Citations
@@ -11,6 +11,8 @@ from agno.run.agent import (
11
11
  OutputModelResponseStartedEvent,
12
12
  ParserModelResponseCompletedEvent,
13
13
  ParserModelResponseStartedEvent,
14
+ PostHookCompletedEvent,
15
+ PostHookStartedEvent,
14
16
  PreHookCompletedEvent,
15
17
  PreHookStartedEvent,
16
18
  ReasoningCompletedEvent,
@@ -18,13 +20,18 @@ from agno.run.agent import (
18
20
  ReasoningStepEvent,
19
21
  RunCancelledEvent,
20
22
  RunCompletedEvent,
23
+ RunContentCompletedEvent,
21
24
  RunContentEvent,
22
25
  RunContinuedEvent,
23
26
  RunErrorEvent,
27
+ RunEvent,
24
28
  RunInput,
25
29
  RunOutput,
30
+ RunOutputEvent,
26
31
  RunPausedEvent,
27
32
  RunStartedEvent,
33
+ SessionSummaryCompletedEvent,
34
+ SessionSummaryStartedEvent,
28
35
  ToolCallCompletedEvent,
29
36
  ToolCallStartedEvent,
30
37
  )
@@ -34,6 +41,8 @@ from agno.run.team import OutputModelResponseCompletedEvent as TeamOutputModelRe
34
41
  from agno.run.team import OutputModelResponseStartedEvent as TeamOutputModelResponseStartedEvent
35
42
  from agno.run.team import ParserModelResponseCompletedEvent as TeamParserModelResponseCompletedEvent
36
43
  from agno.run.team import ParserModelResponseStartedEvent as TeamParserModelResponseStartedEvent
44
+ from agno.run.team import PostHookCompletedEvent as TeamPostHookCompletedEvent
45
+ from agno.run.team import PostHookStartedEvent as TeamPostHookStartedEvent
37
46
  from agno.run.team import PreHookCompletedEvent as TeamPreHookCompletedEvent
38
47
  from agno.run.team import PreHookStartedEvent as TeamPreHookStartedEvent
39
48
  from agno.run.team import ReasoningCompletedEvent as TeamReasoningCompletedEvent
@@ -41,12 +50,16 @@ from agno.run.team import ReasoningStartedEvent as TeamReasoningStartedEvent
41
50
  from agno.run.team import ReasoningStepEvent as TeamReasoningStepEvent
42
51
  from agno.run.team import RunCancelledEvent as TeamRunCancelledEvent
43
52
  from agno.run.team import RunCompletedEvent as TeamRunCompletedEvent
53
+ from agno.run.team import RunContentCompletedEvent as TeamRunContentCompletedEvent
44
54
  from agno.run.team import RunContentEvent as TeamRunContentEvent
45
55
  from agno.run.team import RunErrorEvent as TeamRunErrorEvent
46
56
  from agno.run.team import RunStartedEvent as TeamRunStartedEvent
47
- from agno.run.team import TeamRunInput, TeamRunOutput
57
+ from agno.run.team import SessionSummaryCompletedEvent as TeamSessionSummaryCompletedEvent
58
+ from agno.run.team import SessionSummaryStartedEvent as TeamSessionSummaryStartedEvent
59
+ from agno.run.team import TeamRunEvent, TeamRunInput, TeamRunOutput, TeamRunOutputEvent
48
60
  from agno.run.team import ToolCallCompletedEvent as TeamToolCallCompletedEvent
49
61
  from agno.run.team import ToolCallStartedEvent as TeamToolCallStartedEvent
62
+ from agno.session.summary import SessionSummary
50
63
 
51
64
 
52
65
  def create_team_run_started_event(from_run_response: TeamRunOutput) -> TeamRunStartedEvent:
@@ -242,6 +255,54 @@ def create_team_pre_hook_completed_event(
242
255
  )
243
256
 
244
257
 
258
+ def create_post_hook_started_event(
259
+ from_run_response: RunOutput, post_hook_name: Optional[str] = None
260
+ ) -> PostHookStartedEvent:
261
+ return PostHookStartedEvent(
262
+ session_id=from_run_response.session_id,
263
+ agent_id=from_run_response.agent_id, # type: ignore
264
+ agent_name=from_run_response.agent_name, # type: ignore
265
+ run_id=from_run_response.run_id,
266
+ post_hook_name=post_hook_name,
267
+ )
268
+
269
+
270
+ def create_team_post_hook_started_event(
271
+ from_run_response: TeamRunOutput, post_hook_name: Optional[str] = None
272
+ ) -> TeamPostHookStartedEvent:
273
+ return TeamPostHookStartedEvent(
274
+ session_id=from_run_response.session_id,
275
+ team_id=from_run_response.team_id, # type: ignore
276
+ team_name=from_run_response.team_name, # type: ignore
277
+ run_id=from_run_response.run_id,
278
+ post_hook_name=post_hook_name,
279
+ )
280
+
281
+
282
+ def create_post_hook_completed_event(
283
+ from_run_response: RunOutput, post_hook_name: Optional[str] = None
284
+ ) -> PostHookCompletedEvent:
285
+ return PostHookCompletedEvent(
286
+ session_id=from_run_response.session_id,
287
+ agent_id=from_run_response.agent_id, # type: ignore
288
+ agent_name=from_run_response.agent_name, # type: ignore
289
+ run_id=from_run_response.run_id,
290
+ post_hook_name=post_hook_name,
291
+ )
292
+
293
+
294
+ def create_team_post_hook_completed_event(
295
+ from_run_response: TeamRunOutput, post_hook_name: Optional[str] = None
296
+ ) -> TeamPostHookCompletedEvent:
297
+ return TeamPostHookCompletedEvent(
298
+ session_id=from_run_response.session_id,
299
+ team_id=from_run_response.team_id, # type: ignore
300
+ team_name=from_run_response.team_name, # type: ignore
301
+ run_id=from_run_response.run_id,
302
+ post_hook_name=post_hook_name,
303
+ )
304
+
305
+
245
306
  def create_memory_update_started_event(from_run_response: RunOutput) -> MemoryUpdateStartedEvent:
246
307
  return MemoryUpdateStartedEvent(
247
308
  session_id=from_run_response.session_id,
@@ -278,6 +339,50 @@ def create_team_memory_update_completed_event(from_run_response: TeamRunOutput)
278
339
  )
279
340
 
280
341
 
342
+ def create_team_session_summary_started_event(
343
+ from_run_response: TeamRunOutput,
344
+ ) -> TeamSessionSummaryStartedEvent:
345
+ return TeamSessionSummaryStartedEvent(
346
+ session_id=from_run_response.session_id,
347
+ team_id=from_run_response.team_id, # type: ignore
348
+ team_name=from_run_response.team_name, # type: ignore
349
+ run_id=from_run_response.run_id,
350
+ )
351
+
352
+
353
+ def create_team_session_summary_completed_event(
354
+ from_run_response: TeamRunOutput, session_summary: Optional[SessionSummary] = None
355
+ ) -> TeamSessionSummaryCompletedEvent:
356
+ return TeamSessionSummaryCompletedEvent(
357
+ session_id=from_run_response.session_id,
358
+ team_id=from_run_response.team_id, # type: ignore
359
+ team_name=from_run_response.team_name, # type: ignore
360
+ run_id=from_run_response.run_id,
361
+ session_summary=session_summary,
362
+ )
363
+
364
+
365
+ def create_session_summary_started_event(from_run_response: RunOutput) -> SessionSummaryStartedEvent:
366
+ return SessionSummaryStartedEvent(
367
+ session_id=from_run_response.session_id,
368
+ agent_id=from_run_response.agent_id, # type: ignore
369
+ agent_name=from_run_response.agent_name, # type: ignore
370
+ run_id=from_run_response.run_id,
371
+ )
372
+
373
+
374
+ def create_session_summary_completed_event(
375
+ from_run_response: RunOutput, session_summary: Optional[SessionSummary] = None
376
+ ) -> SessionSummaryCompletedEvent:
377
+ return SessionSummaryCompletedEvent(
378
+ session_id=from_run_response.session_id,
379
+ agent_id=from_run_response.agent_id, # type: ignore
380
+ agent_name=from_run_response.agent_name, # type: ignore
381
+ run_id=from_run_response.run_id,
382
+ session_summary=session_summary,
383
+ )
384
+
385
+
281
386
  def create_reasoning_started_event(from_run_response: RunOutput) -> ReasoningStartedEvent:
282
387
  return ReasoningStartedEvent(
283
388
  session_id=from_run_response.session_id,
@@ -468,6 +573,28 @@ def create_team_run_output_content_event(
468
573
  )
469
574
 
470
575
 
576
+ def create_run_content_completed_event(
577
+ from_run_response: RunOutput,
578
+ ) -> RunContentCompletedEvent:
579
+ return RunContentCompletedEvent(
580
+ session_id=from_run_response.session_id,
581
+ agent_id=from_run_response.agent_id, # type: ignore
582
+ agent_name=from_run_response.agent_name, # type: ignore
583
+ run_id=from_run_response.run_id,
584
+ )
585
+
586
+
587
+ def create_team_run_content_completed_event(
588
+ from_run_response: TeamRunOutput,
589
+ ) -> TeamRunContentCompletedEvent:
590
+ return TeamRunContentCompletedEvent(
591
+ session_id=from_run_response.session_id,
592
+ team_id=from_run_response.team_id, # type: ignore
593
+ team_name=from_run_response.team_name, # type: ignore
594
+ run_id=from_run_response.run_id,
595
+ )
596
+
597
+
471
598
  def create_parser_model_response_started_event(
472
599
  from_run_response: RunOutput,
473
600
  ) -> ParserModelResponseStartedEvent:
@@ -550,3 +677,18 @@ def create_team_output_model_response_completed_event(
550
677
  team_name=from_run_response.team_name, # type: ignore
551
678
  run_id=from_run_response.run_id,
552
679
  )
680
+
681
+
682
+ def handle_event(
683
+ event: Union[RunOutputEvent, TeamRunOutputEvent],
684
+ run_response: Union[RunOutput, TeamRunOutput],
685
+ events_to_skip: Optional[List[Union[RunEvent, TeamRunEvent]]] = None,
686
+ store_events: bool = False,
687
+ ) -> Union[RunOutputEvent, TeamRunOutputEvent]:
688
+ # We only store events that are not run_response_content events
689
+ events_to_skip = [event.value for event in events_to_skip] if events_to_skip else []
690
+ if store_events and event.event not in events_to_skip:
691
+ if run_response.events is None:
692
+ run_response.events = []
693
+ run_response.events.append(event) # type: ignore
694
+ return event