xpander-sdk 2.0.144__py3-none-any.whl → 2.0.192__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 (37) hide show
  1. xpander_sdk/__init__.py +6 -0
  2. xpander_sdk/consts/api_routes.py +9 -0
  3. xpander_sdk/models/activity.py +65 -0
  4. xpander_sdk/models/compactization.py +112 -0
  5. xpander_sdk/models/deep_planning.py +18 -0
  6. xpander_sdk/models/events.py +6 -0
  7. xpander_sdk/models/frameworks.py +2 -2
  8. xpander_sdk/models/generic.py +27 -0
  9. xpander_sdk/models/notifications.py +98 -0
  10. xpander_sdk/models/orchestrations.py +271 -0
  11. xpander_sdk/modules/agents/models/agent.py +11 -5
  12. xpander_sdk/modules/agents/sub_modules/agent.py +25 -10
  13. xpander_sdk/modules/backend/__init__.py +8 -0
  14. xpander_sdk/modules/backend/backend_module.py +47 -2
  15. xpander_sdk/modules/backend/decorators/__init__.py +7 -0
  16. xpander_sdk/modules/backend/decorators/on_auth_event.py +131 -0
  17. xpander_sdk/modules/backend/events_registry.py +172 -0
  18. xpander_sdk/modules/backend/frameworks/agno.py +377 -15
  19. xpander_sdk/modules/backend/frameworks/dispatch.py +3 -1
  20. xpander_sdk/modules/backend/utils/mcp_oauth.py +37 -25
  21. xpander_sdk/modules/events/decorators/__init__.py +3 -0
  22. xpander_sdk/modules/events/decorators/on_tool.py +384 -0
  23. xpander_sdk/modules/events/events_module.py +28 -1
  24. xpander_sdk/modules/tasks/models/task.py +3 -14
  25. xpander_sdk/modules/tasks/sub_modules/task.py +276 -84
  26. xpander_sdk/modules/tools_repository/models/mcp.py +1 -0
  27. xpander_sdk/modules/tools_repository/sub_modules/tool.py +46 -15
  28. xpander_sdk/modules/tools_repository/tools_repository_module.py +6 -2
  29. xpander_sdk/modules/tools_repository/utils/generic.py +3 -0
  30. xpander_sdk/utils/agents/__init__.py +0 -0
  31. xpander_sdk/utils/agents/compactization_agent.py +257 -0
  32. xpander_sdk/utils/generic.py +5 -0
  33. {xpander_sdk-2.0.144.dist-info → xpander_sdk-2.0.192.dist-info}/METADATA +224 -14
  34. {xpander_sdk-2.0.144.dist-info → xpander_sdk-2.0.192.dist-info}/RECORD +37 -24
  35. {xpander_sdk-2.0.144.dist-info → xpander_sdk-2.0.192.dist-info}/WHEEL +0 -0
  36. {xpander_sdk-2.0.144.dist-info → xpander_sdk-2.0.192.dist-info}/licenses/LICENSE +0 -0
  37. {xpander_sdk-2.0.144.dist-info → xpander_sdk-2.0.192.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,7 @@ from enum import Enum
10
10
  from typing import Dict, List, Literal, Optional, Type
11
11
  from pydantic import BaseModel, computed_field
12
12
 
13
+ from xpander_sdk.models.orchestrations import OrchestrationIterativeStrategy, OrchestrationRetryStrategy, OrchestrationStopStrategy
13
14
  from xpander_sdk.models.shared import XPanderSharedModel
14
15
  from xpander_sdk.modules.tools_repository.models.mcp import MCPServerDetails
15
16
 
@@ -383,7 +384,10 @@ class AgentGraphItem(BaseModel):
383
384
  llm_settings: Optional[List[AgentGraphItemLLMSettings]] = []
384
385
  is_first: Optional[bool] = False
385
386
 
386
-
387
+ class LLMReasoningEffort(str, Enum):
388
+ Low = "low"
389
+ Medium = "medium"
390
+ High = "high"
387
391
 
388
392
  class AIAgentConnectivityDetailsA2AAuthType(str, Enum):
389
393
  NoAuth = "none"
@@ -417,12 +421,14 @@ class AgentType(str, Enum):
417
421
  Regular: Standard agent for individual task execution.
418
422
  A2A: Agent that is used via A2A protocol.
419
423
  Curl: Custom Agent that is used via curl.
424
+ Orchestration: marks the agent as an Orchestration object.
420
425
  """
421
426
 
422
427
  Manager = "manager"
423
428
  Regular = "regular"
424
429
  A2A = "a2a"
425
430
  Curl = "curl"
431
+ Orchestration = "orchestration"
426
432
 
427
433
 
428
434
  @dataclass
@@ -468,7 +474,7 @@ class AgentOutput(BaseModel):
468
474
  is_markdown: Optional[bool] = False
469
475
  use_json_mode: Optional[bool] = False
470
476
 
471
- class LLMCredentials(XPanderSharedModel):
472
- name: str
473
- description: Optional[str] = None
474
- value: str
477
+ class TaskLevelStrategies(XPanderSharedModel):
478
+ retry_strategy: Optional[OrchestrationRetryStrategy] = None
479
+ iterative_strategy: Optional[OrchestrationIterativeStrategy] = None
480
+ stop_strategy: Optional[OrchestrationStopStrategy] = None
@@ -19,6 +19,8 @@ from xpander_sdk.core.xpander_api_client import APIClient
19
19
  from xpander_sdk.exceptions.module_exception import ModuleException
20
20
  from xpander_sdk.models.configuration import Configuration
21
21
  from xpander_sdk.models.frameworks import AgnoSettings, Framework
22
+ from xpander_sdk.models.notifications import NotificationSettings
23
+ from xpander_sdk.models.orchestrations import OrchestrationNode
22
24
  from xpander_sdk.modules.agents.utils.generic import get_db_schema_name
23
25
  from xpander_sdk.modules.knowledge_bases.models.knowledge_bases import (
24
26
  KnowledgeBaseSearchResult,
@@ -39,8 +41,10 @@ from xpander_sdk.modules.agents.models.agent import (
39
41
  AgentStatus,
40
42
  AgentType,
41
43
  DatabaseConnectionString,
42
- LLMCredentials,
44
+ LLMReasoningEffort,
45
+ TaskLevelStrategies,
43
46
  )
47
+ from xpander_sdk.models.generic import LLMCredentials
44
48
  from xpander_sdk.modules.agents.models.knowledge_bases import AgentKnowledgeBase
45
49
  from xpander_sdk.modules.knowledge_bases.knowledge_bases_module import KnowledgeBases
46
50
  from xpander_sdk.modules.knowledge_bases.sub_modules.knowledge_base import KnowledgeBase
@@ -151,6 +155,9 @@ class Agent(XPanderSharedModel):
151
155
  using_nemo: Optional[bool]
152
156
  model_provider: str
153
157
  model_name: str
158
+ llm_reasoning_effort: Optional[LLMReasoningEffort] = LLMReasoningEffort.Medium
159
+ deep_planning: Optional[bool] = False
160
+ enforce_deep_planning: Optional[bool] = False
154
161
  llm_api_base: Optional[str]
155
162
  webhook_url: Optional[str]
156
163
  created_at: Optional[datetime]
@@ -161,6 +168,9 @@ class Agent(XPanderSharedModel):
161
168
  llm_credentials: Optional[LLMCredentials]
162
169
  expected_output: Optional[str]
163
170
  agno_settings: Optional[AgnoSettings]
171
+ orchestration_nodes: Optional[List[OrchestrationNode]] = []
172
+ notification_settings: Optional[NotificationSettings] = {}
173
+ task_level_strategies: Optional[TaskLevelStrategies] = None
164
174
 
165
175
  Example:
166
176
  >>> agent = Agent(id="agent123", name="Example Agent")
@@ -192,6 +202,9 @@ class Agent(XPanderSharedModel):
192
202
  using_nemo: Optional[bool] = False
193
203
  model_provider: str
194
204
  model_name: str
205
+ llm_reasoning_effort: Optional[LLMReasoningEffort] = LLMReasoningEffort.Medium
206
+ deep_planning: Optional[bool] = False
207
+ enforce_deep_planning: Optional[bool] = False
195
208
  llm_api_base: Optional[str] = None
196
209
  webhook_url: Optional[str] = None
197
210
  created_at: Optional[datetime] = None
@@ -202,6 +215,9 @@ class Agent(XPanderSharedModel):
202
215
  llm_credentials: Optional[LLMCredentials] = None
203
216
  expected_output: Optional[str] = ""
204
217
  agno_settings: Optional[AgnoSettings] = AgnoSettings()
218
+ orchestration_nodes: Optional[List[OrchestrationNode]] = []
219
+ notification_settings: Optional[NotificationSettings] = {}
220
+ task_level_strategies: Optional[TaskLevelStrategies] = None
205
221
 
206
222
  _connection_string: Optional[DatabaseConnectionString] = None
207
223
 
@@ -621,12 +637,12 @@ class Agent(XPanderSharedModel):
621
637
  schema = get_db_schema_name(agent_id=self.id)
622
638
 
623
639
  client_type = AsyncPostgresDb if async_db else PostgresDb
624
-
640
+
625
641
  return client_type(
626
642
  db_schema=schema,
627
643
  db_url=connection_string.connection_uri.uri.replace("postgresql", "postgresql+psycopg"+("_async" if async_db else "")),
628
644
  )
629
-
645
+
630
646
  def get_db(self) -> Any:
631
647
  """
632
648
  Synchronously retrieve the db for this agent.
@@ -744,9 +760,9 @@ class Agent(XPanderSharedModel):
744
760
  Example:
745
761
  >>> sessions = await agent.aget_user_sessions(user_id="user_123")
746
762
  """
747
- db = await self.aget_db()
763
+ db = await self.aget_db(async_db=True)
748
764
  from agno.db import SessionType
749
- sessions = await asyncio.to_thread(db.get_sessions, user_id=user_id, limit=50, session_type = SessionType.TEAM if self.is_a_team else SessionType.AGENT)
765
+ sessions = await db.get_sessions(user_id=user_id, limit=50, session_type = SessionType.TEAM if self.is_a_team else SessionType.AGENT)
750
766
  return sessions
751
767
 
752
768
  def get_user_sessions(self, user_id: str):
@@ -790,10 +806,9 @@ class Agent(XPanderSharedModel):
790
806
  Example:
791
807
  >>> session = await agent.aget_session(session_id="sess_456")
792
808
  """
793
- db = await self.aget_db()
809
+ db = await self.aget_db(async_db=True)
794
810
  from agno.db import SessionType
795
- session = await asyncio.to_thread(db.get_session, session_id=session_id, session_type = SessionType.TEAM if self.is_a_team else SessionType.AGENT)
796
- return await session
811
+ return await db.get_session(session_id=session_id, session_type = SessionType.TEAM if self.is_a_team else SessionType.AGENT)
797
812
 
798
813
  def get_session(self, session_id: str):
799
814
  """
@@ -834,8 +849,8 @@ class Agent(XPanderSharedModel):
834
849
  Example:
835
850
  >>> await agent.adelete_session(session_id="sess_456")
836
851
  """
837
- db = await self.aget_db(async_db=False)
838
- await asyncio.to_thread(db.delete_session, session_id=session_id)
852
+ db = await self.aget_db(async_db=True)
853
+ await db.delete_session(session_id=session_id)
839
854
 
840
855
  def delete_session(self, session_id: str):
841
856
  """
@@ -0,0 +1,8 @@
1
+ """
2
+ Backend module for xpander.ai SDK.
3
+ """
4
+
5
+ from .backend_module import Backend
6
+ from .decorators.on_auth_event import on_auth_event
7
+
8
+ __all__ = ["Backend", "on_auth_event"]
@@ -231,6 +231,7 @@ class Backend(ModuleBase):
231
231
  override: Optional[Dict[str, Any]] = None,
232
232
  tools: Optional[List[Callable]] = None,
233
233
  is_async: Optional[bool] = True,
234
+ auth_events_callback: Optional[Callable] = None,
234
235
  ) -> Dict[str, Any]:
235
236
  """
236
237
  Asynchronously resolve runtime arguments for the specified agent.
@@ -243,6 +244,27 @@ class Backend(ModuleBase):
243
244
  override (Optional[Dict[str, Any]]): Optional overrides for final arguments.
244
245
  tools (Optional[List[Callable]]): Optional additional tools to be added to the agent arguments.
245
246
  is_async (Optional[bool]): Is in Async Context?.
247
+ auth_events_callback (Optional[Callable]): Optional callback function (async or sync) that will be called for authentication events only.
248
+ Used specifically for authentication events (e.g., MCP OAuth flows requiring user login).
249
+ The callback signature must be: callback(agent: Agent, task: Task, event: TaskUpdateEvent)
250
+
251
+ Can be provided in two ways:
252
+ 1. Direct function: Pass the function directly to this parameter
253
+ 2. Decorator: Use @on_auth_event decorator and pass the decorated function
254
+
255
+ Example (direct function):
256
+ async def my_callback(agent, task, event):
257
+ print(f"Auth required: {event.data}")
258
+ args = await backend.aget_args(agent_id="...", auth_events_callback=my_callback)
259
+
260
+ Example (decorator):
261
+ from xpander_sdk import on_auth_event
262
+
263
+ @on_auth_event
264
+ async def handle_auth(agent, task, event):
265
+ print(f"Auth required: {event.data}")
266
+
267
+ args = await backend.aget_args(agent_id="...", auth_events_callback=handle_auth)
246
268
 
247
269
  Returns:
248
270
  Dict[str, Any]: Resolved argument dictionary to use with the agent.
@@ -267,7 +289,7 @@ class Backend(ModuleBase):
267
289
  "or set via the 'XPANDER_AGENT_ID' environment variable."
268
290
  )
269
291
 
270
- return await dispatch_get_args(agent=xpander_agent, task=task, override=override, tools=tools, is_async=is_async)
292
+ return await dispatch_get_args(agent=xpander_agent, task=task, override=override, tools=tools, is_async=is_async, auth_events_callback=auth_events_callback)
271
293
 
272
294
  def get_args(
273
295
  self,
@@ -277,6 +299,7 @@ class Backend(ModuleBase):
277
299
  task: Optional[Task] = None,
278
300
  override: Optional[Dict[str, Any]] = None,
279
301
  tools: Optional[List[Callable]] = None,
302
+ auth_events_callback: Optional[Callable] = None,
280
303
  ) -> Dict[str, Any]:
281
304
  """
282
305
  Synchronously resolve runtime arguments for the specified agent.
@@ -291,6 +314,27 @@ class Backend(ModuleBase):
291
314
  task (Optional[Task]): Optional Task object providing runtime input/output context.
292
315
  override (Optional[Dict[str, Any]]): Optional overrides for final arguments.
293
316
  tools (Optional[List[Callable]]): Optional additional tools to be added to the agent arguments.
317
+ auth_events_callback (Optional[Callable]): Optional callback function (async or sync) that will be called for authentication events only.
318
+ Used specifically for authentication events (e.g., MCP OAuth flows requiring user login).
319
+ The callback signature must be: callback(agent: Agent, task: Task, event: TaskUpdateEvent)
320
+
321
+ Can be provided in two ways:
322
+ 1. Direct function: Pass the function directly to this parameter
323
+ 2. Decorator: Use @on_auth_event decorator and pass the decorated function
324
+
325
+ Example (direct function):
326
+ def my_callback(agent, task, event):
327
+ print(f"Auth required: {event.data}")
328
+ args = backend.get_args(agent_id="...", auth_events_callback=my_callback)
329
+
330
+ Example (decorator):
331
+ from xpander_sdk import on_auth_event
332
+
333
+ @on_auth_event
334
+ def handle_auth(agent, task, event):
335
+ print(f"Auth required: {event.data}")
336
+
337
+ args = backend.get_args(agent_id="...", auth_events_callback=handle_auth)
294
338
 
295
339
  Returns:
296
340
  Dict[str, Any]: Resolved argument dictionary to use with the agent.
@@ -306,7 +350,8 @@ class Backend(ModuleBase):
306
350
  task=task,
307
351
  override=override,
308
352
  tools=tools,
309
- is_async=False
353
+ is_async=False,
354
+ auth_events_callback=auth_events_callback
310
355
  )
311
356
  )
312
357
 
@@ -0,0 +1,7 @@
1
+ """
2
+ Backend module decorators.
3
+ """
4
+
5
+ from .on_auth_event import on_auth_event
6
+
7
+ __all__ = ["on_auth_event"]
@@ -0,0 +1,131 @@
1
+ """
2
+ xpander_sdk.modules.backend.decorators.on_auth_event
3
+
4
+ This module provides the `@on_auth_event` decorator, which allows developers to define
5
+ authentication event handlers that respond to OAuth flows and authentication events.
6
+
7
+ The decorator ensures that the registered function:
8
+ - Accepts three parameters: agent (Agent), task (Task), and event (TaskUpdateEvent)
9
+ - Handles authentication events (e.g., MCP OAuth flows requiring user login)
10
+ - Can be either synchronous or asynchronous
11
+
12
+ Execution Notes:
13
+ - The handler is called when authentication events occur (e.g., OAuth login required)
14
+ - The event.type will always be "auth_event"
15
+ - The event.data contains authentication-specific information (e.g., OAuth login URL)
16
+ - Use this for displaying authentication prompts, handling OAuth flows, etc.
17
+
18
+ Example usage:
19
+ --------------
20
+ >>> from xpander_sdk import Backend, on_auth_event
21
+ >>>
22
+ >>> # Define handler with decorator - auto-registers globally
23
+ >>> @on_auth_event
24
+ ... async def handle_auth(agent, task, event):
25
+ ... print(f"Authentication required for {agent.name}")
26
+ ... print(f"Login URL: {event.data.get('auth_url')}")
27
+ ... # Display authentication prompt to user
28
+ >>>
29
+ >>> # Handler is automatically invoked when auth events occur
30
+ >>> backend = Backend()
31
+ >>> args = await backend.aget_args(agent_id="agent-123") # handle_auth is called automatically
32
+ """
33
+
34
+ from functools import wraps
35
+ from inspect import iscoroutinefunction, signature
36
+ from typing import Optional, Callable
37
+
38
+ from xpander_sdk.modules.agents.sub_modules.agent import Agent
39
+ from xpander_sdk.modules.tasks.sub_modules.task import Task, TaskUpdateEvent
40
+ from xpander_sdk.modules.backend.events_registry import EventsRegistry, EventType
41
+
42
+
43
+ def on_auth_event(_func: Optional[Callable] = None):
44
+ """
45
+ Decorator to register a handler for authentication events.
46
+
47
+ The decorated function is automatically registered globally and will be called
48
+ whenever authentication events occur during agent execution (e.g., MCP OAuth flows
49
+ requiring user login). The function:
50
+ - Must accept three parameters: agent (Agent), task (Task), event (TaskUpdateEvent)
51
+ - Can be either synchronous or asynchronous
52
+ - Receives only authentication events (event.type == "auth_event")
53
+ - Is invoked automatically - no need to pass it to aget_args/get_args
54
+
55
+ Args:
56
+ _func (Optional[Callable]):
57
+ The function to decorate (for direct usage like `@on_auth_event`).
58
+
59
+ Raises:
60
+ TypeError: If the decorated function does not have the correct parameters.
61
+
62
+ Example:
63
+ >>> @on_auth_event
64
+ ... async def handle_oauth_login(agent, task, event):
65
+ ... print(f"Agent: {agent.name}")
66
+ ... print(f"Task: {task.id}")
67
+ ... print(f"Auth data: {event.data}")
68
+ ... # Handle OAuth flow
69
+
70
+ >>> @on_auth_event
71
+ ... def sync_auth_handler(agent, task, event):
72
+ ... if 'auth_url' in event.data:
73
+ ... print(f"Please visit: {event.data['auth_url']}")
74
+ """
75
+
76
+ def decorator(func: Callable) -> Callable:
77
+ sig = signature(func)
78
+ params = list(sig.parameters.keys())
79
+
80
+ # Validate function signature
81
+ if len(params) < 3:
82
+ raise TypeError(
83
+ f"Function '{func.__name__}' must accept 3 parameters: agent, task, event. "
84
+ f"Got {len(params)} parameters: {params}"
85
+ )
86
+
87
+ # Store the original function for later use
88
+ @wraps(func)
89
+ async def async_wrapper(agent: Agent, task: Task, event: TaskUpdateEvent):
90
+ return await func(agent, task, event)
91
+
92
+ @wraps(func)
93
+ def sync_wrapper(agent: Agent, task: Task, event: TaskUpdateEvent):
94
+ return func(agent, task, event)
95
+
96
+ wrapped = async_wrapper if iscoroutinefunction(func) else sync_wrapper
97
+
98
+ # Mark the function as an auth event handler
99
+ wrapped._is_auth_event_handler = True
100
+
101
+ # Register the handler in the singleton registry
102
+ registry = EventsRegistry()
103
+ registry.register_auth_event(wrapped)
104
+
105
+ return wrapped
106
+
107
+ if _func and callable(_func):
108
+ return decorator(_func)
109
+
110
+ return decorator
111
+
112
+
113
+ def get_registered_handlers():
114
+ """
115
+ Get all registered authentication event handlers.
116
+
117
+ Returns:
118
+ list: List of registered handler functions.
119
+ """
120
+ registry = EventsRegistry()
121
+ return registry.get_auth_handlers()
122
+
123
+
124
+ def clear_handlers():
125
+ """
126
+ Clear all registered authentication event handlers.
127
+
128
+ Useful for testing or when you want to reset the handlers.
129
+ """
130
+ registry = EventsRegistry()
131
+ registry.clear(EventType.AUTH_EVENT)
@@ -0,0 +1,172 @@
1
+ """
2
+ Events Registry - Singleton pattern for managing event handlers and hooks.
3
+
4
+ This registry supports multiple event types:
5
+ - Authentication events (OAuth flows)
6
+ - Tool call hooks (pre/post/error) - Coming soon
7
+ """
8
+
9
+ from typing import Callable, List, Optional, Dict, Any
10
+ from enum import Enum
11
+ import asyncio
12
+
13
+
14
+ class EventType(str, Enum):
15
+ """Supported event types in the backend module."""
16
+ AUTH_EVENT = "auth_event"
17
+ TOOL_PRE_CALL = "tool_pre_call" # Future: before tool execution
18
+ TOOL_POST_CALL = "tool_post_call" # Future: after successful tool execution
19
+ TOOL_ERROR = "tool_error" # Future: when tool execution fails
20
+
21
+
22
+ class EventsRegistry:
23
+ """
24
+ Singleton registry for managing event handlers and hooks.
25
+
26
+ This registry maintains handlers for different event types and provides
27
+ methods to register, retrieve, and invoke them.
28
+
29
+ Supported event types:
30
+ - AUTH_EVENT: Authentication events (e.g., MCP OAuth flows)
31
+ - TOOL_PRE_CALL: Before tool execution (future)
32
+ - TOOL_POST_CALL: After successful tool execution (future)
33
+ - TOOL_ERROR: When tool execution fails (future)
34
+ """
35
+
36
+ _instance: Optional['EventsRegistry'] = None
37
+ _handlers: Dict[EventType, List[Callable]] = {}
38
+
39
+ def __new__(cls):
40
+ if cls._instance is None:
41
+ cls._instance = super().__new__(cls)
42
+ cls._instance._handlers = {
43
+ EventType.AUTH_EVENT: [],
44
+ EventType.TOOL_PRE_CALL: [],
45
+ EventType.TOOL_POST_CALL: [],
46
+ EventType.TOOL_ERROR: [],
47
+ }
48
+ return cls._instance
49
+
50
+ def register(self, event_type: EventType, handler: Callable) -> None:
51
+ """
52
+ Register an event handler for a specific event type.
53
+
54
+ Args:
55
+ event_type (EventType): The type of event to handle.
56
+ handler (Callable): The handler function to register.
57
+ """
58
+ if event_type not in self._handlers:
59
+ self._handlers[event_type] = []
60
+
61
+ if handler not in self._handlers[event_type]:
62
+ self._handlers[event_type].append(handler)
63
+
64
+ def register_auth_event(self, handler: Callable) -> None:
65
+ """
66
+ Register an authentication event handler.
67
+
68
+ Args:
69
+ handler (Callable): The handler function to register.
70
+ """
71
+ self.register(EventType.AUTH_EVENT, handler)
72
+
73
+ def get_handlers(self, event_type: EventType) -> List[Callable]:
74
+ """
75
+ Get all registered handlers for a specific event type.
76
+
77
+ Args:
78
+ event_type (EventType): The type of event.
79
+
80
+ Returns:
81
+ List[Callable]: List of registered handler functions.
82
+ """
83
+ return self._handlers.get(event_type, []).copy()
84
+
85
+ def get_auth_handlers(self) -> List[Callable]:
86
+ """
87
+ Get all registered authentication event handlers.
88
+
89
+ Returns:
90
+ List[Callable]: List of registered auth handler functions.
91
+ """
92
+ return self.get_handlers(EventType.AUTH_EVENT)
93
+
94
+ def clear(self, event_type: Optional[EventType] = None) -> None:
95
+ """
96
+ Clear registered handlers.
97
+
98
+ Args:
99
+ event_type (Optional[EventType]): If provided, clear only handlers
100
+ for this event type. If None, clear all handlers.
101
+ """
102
+ if event_type:
103
+ if event_type in self._handlers:
104
+ self._handlers[event_type].clear()
105
+ else:
106
+ for handlers_list in self._handlers.values():
107
+ handlers_list.clear()
108
+
109
+ async def invoke_handlers(self, event_type: EventType, *args, **kwargs) -> None:
110
+ """
111
+ Invoke all registered handlers for a specific event type.
112
+
113
+ Args:
114
+ event_type (EventType): The type of event to invoke handlers for.
115
+ *args: Positional arguments to pass to handlers.
116
+ **kwargs: Keyword arguments to pass to handlers.
117
+ """
118
+ handlers = self._handlers.get(event_type, [])
119
+
120
+ for handler in handlers:
121
+ try:
122
+ if asyncio.iscoroutinefunction(handler):
123
+ await handler(*args, **kwargs)
124
+ else:
125
+ handler(*args, **kwargs)
126
+ except Exception as e:
127
+ # Log error but continue with other handlers
128
+ from loguru import logger
129
+ logger.error(f"Error in {event_type.value} handler {handler.__name__}: {e}")
130
+
131
+ async def invoke_auth_handlers(self, agent, task, event) -> None:
132
+ """
133
+ Invoke all registered authentication event handlers.
134
+
135
+ Args:
136
+ agent: The Agent object associated with the task.
137
+ task: The Task object being executed.
138
+ event: The TaskUpdateEvent containing authentication event data.
139
+ """
140
+ await self.invoke_handlers(EventType.AUTH_EVENT, agent, task, event)
141
+
142
+ def has_handlers(self, event_type: EventType) -> bool:
143
+ """
144
+ Check if any handlers are registered for a specific event type.
145
+
146
+ Args:
147
+ event_type (EventType): The type of event.
148
+
149
+ Returns:
150
+ bool: True if at least one handler is registered, False otherwise.
151
+ """
152
+ return len(self._handlers.get(event_type, [])) > 0
153
+
154
+ def has_auth_handlers(self) -> bool:
155
+ """
156
+ Check if any authentication event handlers are registered.
157
+
158
+ Returns:
159
+ bool: True if at least one auth handler is registered, False otherwise.
160
+ """
161
+ return self.has_handlers(EventType.AUTH_EVENT)
162
+
163
+ @classmethod
164
+ def reset(cls) -> None:
165
+ """
166
+ Reset the singleton instance.
167
+
168
+ Useful for testing to ensure a clean state.
169
+ """
170
+ if cls._instance is not None:
171
+ cls._instance.clear()
172
+ cls._instance = None