autobyteus 1.0.6__py3-none-any.whl → 1.1.1__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 (270) hide show
  1. autobyteus/agent/agent.py +97 -222
  2. autobyteus/agent/bootstrap_steps/__init__.py +19 -0
  3. autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +88 -0
  4. autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +57 -0
  5. autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +38 -0
  6. autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +93 -0
  7. autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +47 -0
  8. autobyteus/agent/context/__init__.py +13 -0
  9. autobyteus/agent/context/agent_config.py +84 -0
  10. autobyteus/agent/context/agent_context.py +129 -0
  11. autobyteus/agent/context/agent_phase_manager.py +264 -0
  12. autobyteus/agent/context/agent_runtime_state.py +89 -0
  13. autobyteus/agent/context/phases.py +49 -0
  14. autobyteus/agent/events/__init__.py +52 -0
  15. autobyteus/agent/events/agent_events.py +110 -0
  16. autobyteus/agent/events/agent_input_event_queue_manager.py +174 -0
  17. autobyteus/agent/events/notifiers.py +122 -0
  18. autobyteus/agent/events/worker_event_dispatcher.py +118 -0
  19. autobyteus/agent/factory/__init__.py +9 -0
  20. autobyteus/agent/factory/agent_factory.py +145 -0
  21. autobyteus/agent/group/agent_group.py +164 -0
  22. autobyteus/agent/group/agent_group_context.py +81 -0
  23. autobyteus/agent/handlers/__init__.py +36 -0
  24. autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +148 -0
  25. autobyteus/agent/handlers/base_event_handler.py +36 -0
  26. autobyteus/agent/handlers/event_handler_registry.py +76 -0
  27. autobyteus/agent/handlers/generic_event_handler.py +46 -0
  28. autobyteus/agent/handlers/inter_agent_message_event_handler.py +76 -0
  29. autobyteus/agent/handlers/lifecycle_event_logger.py +64 -0
  30. autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +138 -0
  31. autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +140 -0
  32. autobyteus/agent/handlers/tool_execution_approval_event_handler.py +85 -0
  33. autobyteus/agent/handlers/tool_invocation_request_event_handler.py +211 -0
  34. autobyteus/agent/handlers/tool_result_event_handler.py +101 -0
  35. autobyteus/agent/handlers/user_input_message_event_handler.py +77 -0
  36. autobyteus/agent/hooks/__init__.py +16 -0
  37. autobyteus/agent/hooks/base_phase_hook.py +61 -0
  38. autobyteus/agent/hooks/hook_definition.py +36 -0
  39. autobyteus/agent/hooks/hook_meta.py +37 -0
  40. autobyteus/agent/hooks/hook_registry.py +118 -0
  41. autobyteus/agent/input_processor/__init__.py +18 -0
  42. autobyteus/agent/input_processor/base_user_input_processor.py +54 -0
  43. autobyteus/agent/input_processor/content_prefixing_input_processor.py +41 -0
  44. autobyteus/agent/input_processor/metadata_appending_input_processor.py +34 -0
  45. autobyteus/agent/input_processor/passthrough_input_processor.py +33 -0
  46. autobyteus/agent/input_processor/processor_definition.py +42 -0
  47. autobyteus/agent/input_processor/processor_meta.py +46 -0
  48. autobyteus/agent/input_processor/processor_registry.py +117 -0
  49. autobyteus/agent/llm_response_processor/__init__.py +16 -0
  50. autobyteus/agent/llm_response_processor/base_processor.py +53 -0
  51. autobyteus/agent/llm_response_processor/processor_definition.py +36 -0
  52. autobyteus/agent/llm_response_processor/processor_meta.py +37 -0
  53. autobyteus/agent/llm_response_processor/processor_registry.py +113 -0
  54. autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +54 -0
  55. autobyteus/agent/message/__init__.py +20 -0
  56. autobyteus/agent/message/agent_input_user_message.py +96 -0
  57. autobyteus/agent/message/context_file.py +82 -0
  58. autobyteus/agent/message/context_file_type.py +63 -0
  59. autobyteus/agent/message/{message.py → inter_agent_message.py} +12 -12
  60. autobyteus/agent/message/{message_types.py → inter_agent_message_type.py} +8 -6
  61. autobyteus/agent/message/send_message_to.py +142 -36
  62. autobyteus/agent/phases/__init__.py +18 -0
  63. autobyteus/agent/phases/discover.py +52 -0
  64. autobyteus/agent/phases/manager.py +265 -0
  65. autobyteus/agent/phases/phase_enum.py +49 -0
  66. autobyteus/agent/phases/transition_decorator.py +40 -0
  67. autobyteus/agent/phases/transition_info.py +33 -0
  68. autobyteus/agent/remote_agent.py +244 -0
  69. autobyteus/agent/runtime/__init__.py +15 -0
  70. autobyteus/agent/runtime/agent_runtime.py +137 -0
  71. autobyteus/agent/runtime/agent_thread_pool_manager.py +88 -0
  72. autobyteus/agent/runtime/agent_worker.py +200 -0
  73. autobyteus/agent/streaming/__init__.py +15 -0
  74. autobyteus/agent/streaming/agent_event_stream.py +173 -0
  75. autobyteus/agent/streaming/queue_streamer.py +58 -0
  76. autobyteus/agent/streaming/stream_event_payloads.py +167 -0
  77. autobyteus/agent/streaming/stream_events.py +126 -0
  78. autobyteus/agent/system_prompt_processor/__init__.py +14 -0
  79. autobyteus/agent/system_prompt_processor/base_processor.py +48 -0
  80. autobyteus/agent/system_prompt_processor/processor_definition.py +40 -0
  81. autobyteus/agent/system_prompt_processor/processor_meta.py +47 -0
  82. autobyteus/agent/system_prompt_processor/processor_registry.py +119 -0
  83. autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +79 -0
  84. autobyteus/agent/tool_invocation.py +54 -5
  85. autobyteus/agent/utils/__init__.py +9 -0
  86. autobyteus/agent/utils/wait_for_idle.py +59 -0
  87. autobyteus/agent/workflow/__init__.py +11 -0
  88. autobyteus/agent/workflow/agentic_workflow.py +89 -0
  89. autobyteus/agent/workflow/base_agentic_workflow.py +98 -0
  90. autobyteus/agent/workspace/__init__.py +11 -0
  91. autobyteus/agent/workspace/base_workspace.py +77 -0
  92. autobyteus/agent/workspace/workspace_config.py +160 -0
  93. autobyteus/agent/workspace/workspace_definition.py +36 -0
  94. autobyteus/agent/workspace/workspace_meta.py +37 -0
  95. autobyteus/agent/workspace/workspace_registry.py +72 -0
  96. autobyteus/cli/__init__.py +11 -0
  97. autobyteus/cli/agent_cli.py +117 -0
  98. autobyteus/cli/cli_display.py +205 -0
  99. autobyteus/events/event_emitter.py +33 -56
  100. autobyteus/events/event_manager.py +134 -66
  101. autobyteus/events/event_types.py +43 -14
  102. autobyteus/llm/api/autobyteus_llm.py +13 -25
  103. autobyteus/llm/api/bedrock_llm.py +9 -3
  104. autobyteus/llm/api/claude_llm.py +10 -5
  105. autobyteus/llm/api/deepseek_llm.py +55 -93
  106. autobyteus/llm/api/gemini_llm.py +10 -4
  107. autobyteus/llm/api/grok_llm.py +55 -79
  108. autobyteus/llm/api/groq_llm.py +10 -5
  109. autobyteus/llm/api/mistral_llm.py +13 -8
  110. autobyteus/llm/api/nvidia_llm.py +9 -4
  111. autobyteus/llm/api/ollama_llm.py +58 -50
  112. autobyteus/llm/api/openai_llm.py +20 -14
  113. autobyteus/llm/base_llm.py +95 -34
  114. autobyteus/llm/extensions/base_extension.py +3 -4
  115. autobyteus/llm/extensions/token_usage_tracking_extension.py +13 -4
  116. autobyteus/llm/llm_factory.py +116 -53
  117. autobyteus/llm/models.py +92 -17
  118. autobyteus/llm/ollama_provider.py +6 -2
  119. autobyteus/llm/ollama_provider_resolver.py +44 -0
  120. autobyteus/llm/user_message.py +73 -0
  121. autobyteus/llm/utils/llm_config.py +124 -27
  122. autobyteus/llm/utils/response_types.py +3 -2
  123. autobyteus/llm/utils/token_usage.py +7 -4
  124. autobyteus/rpc/__init__.py +73 -0
  125. autobyteus/rpc/client/__init__.py +17 -0
  126. autobyteus/rpc/client/abstract_client_connection.py +124 -0
  127. autobyteus/rpc/client/client_connection_manager.py +153 -0
  128. autobyteus/rpc/client/sse_client_connection.py +306 -0
  129. autobyteus/rpc/client/stdio_client_connection.py +280 -0
  130. autobyteus/rpc/config/__init__.py +13 -0
  131. autobyteus/rpc/config/agent_server_config.py +153 -0
  132. autobyteus/rpc/config/agent_server_registry.py +152 -0
  133. autobyteus/rpc/hosting.py +244 -0
  134. autobyteus/rpc/protocol.py +244 -0
  135. autobyteus/rpc/server/__init__.py +20 -0
  136. autobyteus/rpc/server/agent_server_endpoint.py +181 -0
  137. autobyteus/rpc/server/base_method_handler.py +40 -0
  138. autobyteus/rpc/server/method_handlers.py +259 -0
  139. autobyteus/rpc/server/sse_server_handler.py +182 -0
  140. autobyteus/rpc/server/stdio_server_handler.py +151 -0
  141. autobyteus/rpc/server_main.py +198 -0
  142. autobyteus/rpc/transport_type.py +13 -0
  143. autobyteus/tools/__init__.py +77 -0
  144. autobyteus/tools/ask_user_input.py +34 -77
  145. autobyteus/tools/base_tool.py +73 -38
  146. autobyteus/tools/bash/__init__.py +2 -0
  147. autobyteus/tools/bash/bash_executor.py +42 -79
  148. autobyteus/tools/browser/__init__.py +2 -0
  149. autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +50 -42
  150. autobyteus/tools/browser/session_aware/browser_session_aware_tool.py +7 -4
  151. autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +117 -125
  152. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +75 -22
  153. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +94 -28
  154. autobyteus/tools/browser/session_aware/factory/browser_session_aware_web_element_trigger_factory.py +10 -2
  155. autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_reader_factory.py +18 -2
  156. autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_screenshot_taker_factory.py +10 -2
  157. autobyteus/tools/browser/session_aware/shared_browser_session_manager.py +4 -3
  158. autobyteus/tools/browser/standalone/__init__.py +7 -0
  159. autobyteus/tools/browser/standalone/factory/google_search_factory.py +17 -2
  160. autobyteus/tools/browser/standalone/factory/webpage_reader_factory.py +17 -2
  161. autobyteus/tools/browser/standalone/factory/webpage_screenshot_taker_factory.py +10 -2
  162. autobyteus/tools/browser/standalone/google_search_ui.py +104 -67
  163. autobyteus/tools/browser/standalone/navigate_to.py +52 -28
  164. autobyteus/tools/browser/standalone/web_page_pdf_generator.py +94 -0
  165. autobyteus/tools/browser/standalone/webpage_image_downloader.py +146 -61
  166. autobyteus/tools/browser/standalone/webpage_reader.py +80 -61
  167. autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +91 -45
  168. autobyteus/tools/factory/__init__.py +9 -0
  169. autobyteus/tools/factory/tool_factory.py +25 -4
  170. autobyteus/tools/file/file_reader.py +22 -51
  171. autobyteus/tools/file/file_writer.py +25 -56
  172. autobyteus/tools/functional_tool.py +249 -0
  173. autobyteus/tools/image_downloader.py +49 -71
  174. autobyteus/tools/mcp/__init__.py +47 -0
  175. autobyteus/tools/mcp/call_handlers/__init__.py +18 -0
  176. autobyteus/tools/mcp/call_handlers/base_handler.py +40 -0
  177. autobyteus/tools/mcp/call_handlers/sse_handler.py +22 -0
  178. autobyteus/tools/mcp/call_handlers/stdio_handler.py +76 -0
  179. autobyteus/tools/mcp/call_handlers/streamable_http_handler.py +55 -0
  180. autobyteus/tools/mcp/config_service.py +237 -0
  181. autobyteus/tools/mcp/factory.py +70 -0
  182. autobyteus/tools/mcp/registrar.py +323 -0
  183. autobyteus/tools/mcp/schema_mapper.py +131 -0
  184. autobyteus/tools/mcp/tool.py +101 -0
  185. autobyteus/tools/mcp/types.py +98 -0
  186. autobyteus/tools/parameter_schema.py +268 -0
  187. autobyteus/tools/pdf_downloader.py +78 -79
  188. autobyteus/tools/registry/__init__.py +0 -2
  189. autobyteus/tools/registry/tool_definition.py +113 -34
  190. autobyteus/tools/registry/tool_registry.py +64 -22
  191. autobyteus/tools/timer.py +150 -102
  192. autobyteus/tools/tool_category.py +11 -0
  193. autobyteus/tools/tool_config.py +117 -0
  194. autobyteus/tools/tool_meta.py +50 -26
  195. autobyteus/tools/tool_state.py +20 -0
  196. autobyteus/tools/usage/__init__.py +6 -0
  197. autobyteus/tools/usage/formatters/__init__.py +31 -0
  198. autobyteus/tools/usage/formatters/anthropic_json_example_formatter.py +18 -0
  199. autobyteus/tools/usage/formatters/anthropic_json_schema_formatter.py +25 -0
  200. autobyteus/tools/usage/formatters/base_formatter.py +42 -0
  201. autobyteus/tools/usage/formatters/default_json_example_formatter.py +42 -0
  202. autobyteus/tools/usage/formatters/default_json_schema_formatter.py +28 -0
  203. autobyteus/tools/usage/formatters/default_xml_example_formatter.py +55 -0
  204. autobyteus/tools/usage/formatters/default_xml_schema_formatter.py +46 -0
  205. autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +34 -0
  206. autobyteus/tools/usage/formatters/gemini_json_schema_formatter.py +25 -0
  207. autobyteus/tools/usage/formatters/google_json_example_formatter.py +34 -0
  208. autobyteus/tools/usage/formatters/google_json_schema_formatter.py +25 -0
  209. autobyteus/tools/usage/formatters/openai_json_example_formatter.py +49 -0
  210. autobyteus/tools/usage/formatters/openai_json_schema_formatter.py +28 -0
  211. autobyteus/tools/usage/parsers/__init__.py +22 -0
  212. autobyteus/tools/usage/parsers/anthropic_xml_tool_usage_parser.py +10 -0
  213. autobyteus/tools/usage/parsers/base_parser.py +41 -0
  214. autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +106 -0
  215. autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +136 -0
  216. autobyteus/tools/usage/parsers/exceptions.py +13 -0
  217. autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +66 -0
  218. autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +196 -0
  219. autobyteus/tools/usage/parsers/provider_aware_tool_usage_parser.py +67 -0
  220. autobyteus/tools/usage/providers/__init__.py +22 -0
  221. autobyteus/tools/usage/providers/json_example_provider.py +32 -0
  222. autobyteus/tools/usage/providers/json_schema_provider.py +35 -0
  223. autobyteus/tools/usage/providers/json_tool_usage_parser_provider.py +28 -0
  224. autobyteus/tools/usage/providers/tool_manifest_provider.py +68 -0
  225. autobyteus/tools/usage/providers/xml_example_provider.py +28 -0
  226. autobyteus/tools/usage/providers/xml_schema_provider.py +29 -0
  227. autobyteus/tools/usage/providers/xml_tool_usage_parser_provider.py +26 -0
  228. autobyteus/tools/usage/registries/__init__.py +20 -0
  229. autobyteus/tools/usage/registries/json_example_formatter_registry.py +51 -0
  230. autobyteus/tools/usage/registries/json_schema_formatter_registry.py +51 -0
  231. autobyteus/tools/usage/registries/json_tool_usage_parser_registry.py +42 -0
  232. autobyteus/tools/usage/registries/xml_example_formatter_registry.py +30 -0
  233. autobyteus/tools/usage/registries/xml_schema_formatter_registry.py +33 -0
  234. autobyteus/tools/usage/registries/xml_tool_usage_parser_registry.py +30 -0
  235. {autobyteus-1.0.6.dist-info → autobyteus-1.1.1.dist-info}/METADATA +23 -5
  236. autobyteus-1.1.1.dist-info/RECORD +296 -0
  237. {autobyteus-1.0.6.dist-info → autobyteus-1.1.1.dist-info}/WHEEL +1 -1
  238. autobyteus/agent/async_agent.py +0 -175
  239. autobyteus/agent/async_group_aware_agent.py +0 -136
  240. autobyteus/agent/group/async_group_aware_agent.py +0 -122
  241. autobyteus/agent/group/coordinator_agent.py +0 -36
  242. autobyteus/agent/group/group_aware_agent.py +0 -121
  243. autobyteus/agent/orchestrator/__init__.py +0 -0
  244. autobyteus/agent/orchestrator/base_agent_orchestrator.py +0 -82
  245. autobyteus/agent/orchestrator/multi_replica_agent_orchestrator.py +0 -72
  246. autobyteus/agent/orchestrator/single_replica_agent_orchestrator.py +0 -43
  247. autobyteus/agent/response_parser/__init__.py +0 -0
  248. autobyteus/agent/response_parser/tool_usage_command_parser.py +0 -100
  249. autobyteus/agent/status.py +0 -12
  250. autobyteus/conversation/__init__.py +0 -0
  251. autobyteus/conversation/conversation.py +0 -54
  252. autobyteus/conversation/user_message.py +0 -59
  253. autobyteus/events/decorators.py +0 -29
  254. autobyteus/prompt/prompt_version_manager.py +0 -58
  255. autobyteus/prompt/storage/__init__.py +0 -0
  256. autobyteus/prompt/storage/prompt_version_model.py +0 -29
  257. autobyteus/prompt/storage/prompt_version_repository.py +0 -83
  258. autobyteus/tools/bash/factory/__init__.py +0 -0
  259. autobyteus/tools/bash/factory/bash_executor_factory.py +0 -6
  260. autobyteus/tools/factory/ask_user_input_factory.py +0 -6
  261. autobyteus/tools/factory/image_downloader_factory.py +0 -9
  262. autobyteus/tools/factory/pdf_downloader_factory.py +0 -9
  263. autobyteus/tools/factory/webpage_image_downloader_factory.py +0 -6
  264. autobyteus/tools/file/factory/__init__.py +0 -0
  265. autobyteus/tools/file/factory/file_reader_factory.py +0 -6
  266. autobyteus/tools/file/factory/file_writer_factory.py +0 -6
  267. autobyteus/tools/web_page_pdf_generator.py +0 -90
  268. autobyteus-1.0.6.dist-info/RECORD +0 -157
  269. {autobyteus-1.0.6.dist-info → autobyteus-1.1.1.dist-info}/licenses/LICENSE +0 -0
  270. {autobyteus-1.0.6.dist-info → autobyteus-1.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,211 @@
1
+ # file: autobyteus/autobyteus/agent/handlers/tool_invocation_request_event_handler.py
2
+ import logging
3
+ import json
4
+ import traceback
5
+ from typing import TYPE_CHECKING, Optional
6
+
7
+ from autobyteus.agent.handlers.base_event_handler import AgentEventHandler
8
+ from autobyteus.agent.events import PendingToolInvocationEvent, ToolResultEvent
9
+ from autobyteus.agent.tool_invocation import ToolInvocation
10
+
11
+ if TYPE_CHECKING:
12
+ from autobyteus.agent.context import AgentContext
13
+ from autobyteus.agent.events.notifiers import AgentExternalEventNotifier
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class ToolInvocationRequestEventHandler(AgentEventHandler):
18
+ """
19
+ Handles PendingToolInvocationEvents.
20
+ If 'auto_execute_tools' (from AgentConfig) is False, it stores the invocation,
21
+ updates history, and emits an AGENT_REQUEST_TOOL_INVOCATION_APPROVAL event via the notifier.
22
+ If 'auto_execute_tools' is True, it executes the tool directly, emits
23
+ AGENT_DATA_TOOL_LOG events for call and result/error,
24
+ and queues a ToolResultEvent.
25
+ """
26
+ def __init__(self): # pragma: no cover
27
+ logger.info("ToolInvocationRequestEventHandler initialized.")
28
+
29
+ async def _execute_tool_directly(self,
30
+ tool_invocation: ToolInvocation,
31
+ context: 'AgentContext',
32
+ notifier: Optional['AgentExternalEventNotifier']) -> None: # pragma: no cover
33
+ agent_id = context.agent_id
34
+ tool_name = tool_invocation.name
35
+ arguments = tool_invocation.arguments
36
+ invocation_id = tool_invocation.id
37
+
38
+ if notifier:
39
+ try:
40
+ auto_exec_data = {
41
+ "invocation_id": invocation_id,
42
+ "tool_name": tool_name,
43
+ "arguments": arguments,
44
+ }
45
+ notifier.notify_agent_tool_invocation_auto_executing(auto_exec_data)
46
+ except Exception as e_notify:
47
+ logger.error(f"Agent '{agent_id}': Error notifying tool auto-execution: {e_notify}", exc_info=True)
48
+
49
+ logger.info(f"Agent '{agent_id}' executing tool directly: '{tool_name}' (ID: {invocation_id}) with args: {arguments}")
50
+
51
+ try:
52
+ args_str = json.dumps(arguments)
53
+ except TypeError:
54
+ args_str = str(arguments)
55
+
56
+ log_msg_call = f"[TOOL_CALL_DIRECT] Agent_ID: {agent_id}, Tool: {tool_name}, Invocation_ID: {invocation_id}, Arguments: {args_str}"
57
+ if notifier:
58
+ try:
59
+ log_data = {
60
+ "log_entry": log_msg_call,
61
+ "tool_invocation_id": invocation_id,
62
+ "tool_name": tool_name,
63
+ }
64
+ notifier.notify_agent_data_tool_log(log_data)
65
+ except Exception as e_notify:
66
+ logger.error(f"Agent '{agent_id}': Error notifying tool call log: {e_notify}", exc_info=True)
67
+
68
+ tool_instance = context.get_tool(tool_name)
69
+ result_event: ToolResultEvent
70
+
71
+ if not tool_instance:
72
+ error_message = f"Tool '{tool_name}' not found or configured for agent '{agent_id}'."
73
+ logger.error(error_message)
74
+ result_event = ToolResultEvent(tool_name=tool_name, result=None, error=error_message, tool_invocation_id=invocation_id)
75
+ context.add_message_to_history({
76
+ "role": "tool",
77
+ "tool_call_id": invocation_id,
78
+ "name": tool_name,
79
+ "content": f"Error: Tool '{tool_name}' execution failed. Reason: {error_message}",
80
+ })
81
+ log_msg_error = f"[TOOL_ERROR_DIRECT] {error_message}"
82
+ if notifier:
83
+ try:
84
+ # Log entry
85
+ log_data = { "log_entry": log_msg_error, "tool_invocation_id": invocation_id, "tool_name": tool_name, }
86
+ notifier.notify_agent_data_tool_log(log_data)
87
+ # Generic output error
88
+ notifier.notify_agent_error_output_generation(
89
+ error_source=f"ToolExecutionDirect.ToolNotFound.{tool_name}",
90
+ error_message=error_message
91
+ )
92
+ except Exception as e_notify:
93
+ logger.error(f"Agent '{agent_id}': Error notifying tool error log/output error: {e_notify}", exc_info=True)
94
+ else:
95
+ try:
96
+ logger.debug(f"Executing tool '{tool_name}' for agent '{agent_id}'. Invocation ID: {invocation_id}")
97
+ execution_result = await tool_instance.execute(context=context, **arguments)
98
+
99
+ try:
100
+ result_json_for_log = json.dumps(execution_result)
101
+ except (TypeError, ValueError):
102
+ result_json_for_log = json.dumps(str(execution_result))
103
+
104
+ logger.info(f"Tool '{tool_name}' (ID: {invocation_id}) executed by agent '{agent_id}'.")
105
+ result_event = ToolResultEvent(tool_name=tool_name, result=execution_result, error=None, tool_invocation_id=invocation_id)
106
+
107
+ history_content = str(execution_result)
108
+ context.add_message_to_history({
109
+ "role": "tool",
110
+ "tool_call_id": invocation_id,
111
+ "name": tool_name,
112
+ "content": history_content,
113
+ })
114
+ log_msg_result = f"[TOOL_RESULT_DIRECT] {result_json_for_log}"
115
+ if notifier:
116
+ try:
117
+ # Log entry with embedded JSON result
118
+ log_data = { "log_entry": log_msg_result, "tool_invocation_id": invocation_id, "tool_name": tool_name }
119
+ notifier.notify_agent_data_tool_log(log_data)
120
+ except Exception as e_notify:
121
+ logger.error(f"Agent '{agent_id}': Error notifying tool result log: {e_notify}", exc_info=True)
122
+
123
+ except Exception as e:
124
+ error_message = f"Error executing tool '{tool_name}' (ID: {invocation_id}): {str(e)}"
125
+ error_details = traceback.format_exc()
126
+ logger.error(f"Agent '{agent_id}' {error_message}", exc_info=True)
127
+ result_event = ToolResultEvent(tool_name=tool_name, result=None, error=error_message, tool_invocation_id=invocation_id)
128
+ context.add_message_to_history({
129
+ "role": "tool",
130
+ "tool_call_id": invocation_id,
131
+ "name": tool_name,
132
+ "content": f"Error: Tool '{tool_name}' execution failed. Reason: {error_message}",
133
+ })
134
+ log_msg_exception = f"[TOOL_EXCEPTION_DIRECT] {error_message}\nDetails:\n{error_details}"
135
+ if notifier:
136
+ try:
137
+ # Log entry
138
+ log_data = { "log_entry": log_msg_exception, "tool_invocation_id": invocation_id, "tool_name": tool_name }
139
+ notifier.notify_agent_data_tool_log(log_data)
140
+ # Generic output error
141
+ notifier.notify_agent_error_output_generation(
142
+ error_source=f"ToolExecutionDirect.Exception.{tool_name}",
143
+ error_message=error_message,
144
+ error_details=error_details
145
+ )
146
+ except Exception as e_notify:
147
+ logger.error(f"Agent '{agent_id}': Error notifying tool exception log/output error: {e_notify}", exc_info=True)
148
+
149
+ await context.input_event_queues.enqueue_tool_result(result_event)
150
+ logger.debug(f"Agent '{agent_id}' enqueued ToolResultEvent (direct exec) for '{tool_name}' (ID: {invocation_id}).")
151
+
152
+
153
+ async def handle(self,
154
+ event: PendingToolInvocationEvent,
155
+ context: 'AgentContext') -> None: # pragma: no cover
156
+ if not isinstance(event, PendingToolInvocationEvent):
157
+ logger.warning(f"ToolInvocationRequestEventHandler received non-PendingToolInvocationEvent: {type(event)}. Skipping.")
158
+ return
159
+
160
+ tool_invocation: ToolInvocation = event.tool_invocation
161
+ agent_id = context.agent_id
162
+
163
+ notifier: Optional['AgentExternalEventNotifier'] = None
164
+ if context.phase_manager:
165
+ notifier = context.phase_manager.notifier
166
+
167
+ if not notifier:
168
+ logger.error(f"Agent '{agent_id}': Notifier not available in ToolInvocationRequestEventHandler. Output events for tool approval/logging will be lost.")
169
+ if not context.auto_execute_tools:
170
+ logger.critical(f"Agent '{agent_id}': Notifier is REQUIRED for manual tool approval flow but is unavailable. Tool '{tool_invocation.name}' cannot be processed for approval.")
171
+ return
172
+
173
+ if not context.auto_execute_tools:
174
+ logger.info(f"Agent '{agent_id}': Tool '{tool_invocation.name}' (ID: {tool_invocation.id}) requires approval. Storing pending invocation and emitting request.")
175
+
176
+ context.store_pending_tool_invocation(tool_invocation)
177
+
178
+ try:
179
+ arguments_json_str = json.dumps(tool_invocation.arguments or {})
180
+ except TypeError:
181
+ logger.warning(f"Could not serialize args for history tool_call for '{tool_invocation.name}'. Using empty dict string.")
182
+ arguments_json_str = "{}"
183
+
184
+ context.add_message_to_history({
185
+ "role": "assistant",
186
+ "content": None,
187
+ "tool_calls": [{
188
+ "id": tool_invocation.id,
189
+ "type": "function",
190
+ "function": {
191
+ "name": tool_invocation.name,
192
+ "arguments": arguments_json_str
193
+ }
194
+ }]
195
+ })
196
+
197
+ approval_data = {
198
+ "invocation_id": tool_invocation.id,
199
+ "tool_name": tool_invocation.name,
200
+ "arguments": tool_invocation.arguments,
201
+ }
202
+ if notifier:
203
+ try:
204
+ notifier.notify_agent_request_tool_invocation_approval(approval_data)
205
+ logger.debug(f"Agent '{agent_id}': Emitted AGENT_REQUEST_TOOL_INVOCATION_APPROVAL for '{tool_invocation.name}' (ID: {tool_invocation.id}).")
206
+ except Exception as e_notify:
207
+ logger.error(f"Agent '{agent_id}': Error emitting AGENT_REQUEST_TOOL_INVOCATION_APPROVAL: {e_notify}", exc_info=True)
208
+
209
+ else:
210
+ logger.info(f"Agent '{agent_id}': Tool '{tool_invocation.name}' (ID: {tool_invocation.id}) executing automatically (auto_execute_tools=True).")
211
+ await self._execute_tool_directly(tool_invocation, context, notifier)
@@ -0,0 +1,101 @@
1
+ # file: autobyteus/autobyteus/agent/handlers/tool_result_event_handler.py
2
+ import logging
3
+ import json
4
+ from typing import TYPE_CHECKING, Optional
5
+
6
+ from autobyteus.agent.handlers.base_event_handler import AgentEventHandler
7
+ from autobyteus.agent.events import ToolResultEvent, LLMUserMessageReadyEvent
8
+ from autobyteus.llm.user_message import LLMUserMessage
9
+
10
+ if TYPE_CHECKING:
11
+ from autobyteus.agent.context import AgentContext
12
+ from autobyteus.agent.events.notifiers import AgentExternalEventNotifier
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class ToolResultEventHandler(AgentEventHandler):
17
+ """
18
+ Handles ToolResultEvents by formatting the tool's output (or error)
19
+ as a new LLMUserMessage, emitting AGENT_DATA_TOOL_LOG event for this outcome,
20
+ and enqueuing an LLMUserMessageReadyEvent for further LLM processing.
21
+ """
22
+ def __init__(self):
23
+ logger.info("ToolResultEventHandler initialized.")
24
+
25
+ async def handle(self,
26
+ event: ToolResultEvent,
27
+ context: 'AgentContext') -> None:
28
+ if not isinstance(event, ToolResultEvent):
29
+ logger.warning(f"ToolResultEventHandler received non-ToolResultEvent: {type(event)}. Skipping.")
30
+ return
31
+
32
+ agent_id = context.agent_id
33
+ tool_invocation_id = event.tool_invocation_id if event.tool_invocation_id else 'N/A'
34
+
35
+ logger.info(f"Agent '{agent_id}' handling ToolResultEvent from tool: '{event.tool_name}' (Invocation ID: {tool_invocation_id}). Error: {event.error is not None}")
36
+
37
+ notifier: Optional['AgentExternalEventNotifier'] = None
38
+ if context.phase_manager:
39
+ notifier = context.phase_manager.notifier
40
+
41
+ if not notifier: # pragma: no cover
42
+ logger.error(f"Agent '{agent_id}': Notifier not available in ToolResultEventHandler. Tool result processing logs will not be emitted.")
43
+
44
+ if event.error:
45
+ logger.debug(f"Agent '{agent_id}' tool '{event.tool_name}' (ID: {tool_invocation_id}) raw error details: {event.error}")
46
+ else:
47
+ try:
48
+ raw_result_str_for_debug_log = json.dumps(event.result, indent=2)
49
+ except TypeError: # pragma: no cover
50
+ raw_result_str_for_debug_log = str(event.result)
51
+ logger.debug(f"Agent '{agent_id}' tool '{event.tool_name}' (ID: {tool_invocation_id}) raw result:\n---\n{raw_result_str_for_debug_log}\n---")
52
+
53
+
54
+ content_for_llm: str
55
+ if event.error:
56
+ content_for_llm = (
57
+ f"The tool '{event.tool_name}' (invocation ID: {tool_invocation_id}) encountered an error.\n"
58
+ f"Error details: {event.error}\n"
59
+ f"Please analyze this error and decide the next course of action."
60
+ )
61
+ log_msg_error_processed = f"[TOOL_RESULT_ERROR_PROCESSED] Agent_ID: {agent_id}, Tool: {event.tool_name}, Invocation_ID: {tool_invocation_id}, Error: {event.error}"
62
+ if notifier:
63
+ try:
64
+ log_data = {
65
+ "log_entry": log_msg_error_processed,
66
+ "tool_invocation_id": tool_invocation_id,
67
+ "tool_name": event.tool_name,
68
+ }
69
+ notifier.notify_agent_data_tool_log(log_data)
70
+ except Exception as e_notify:
71
+ logger.error(f"Agent '{agent_id}': Error notifying tool result error log: {e_notify}", exc_info=True)
72
+ else:
73
+ try:
74
+ result_str_for_llm = json.dumps(event.result, indent=2) if not isinstance(event.result, str) else event.result
75
+ except TypeError: # pragma: no cover
76
+ result_str_for_llm = str(event.result)
77
+
78
+ content_for_llm = (
79
+ f"The tool '{event.tool_name}' (invocation ID: {tool_invocation_id}) has executed.\n"
80
+ f"Result:\n{result_str_for_llm}\n"
81
+ f"Based on this result, what is the next step or final answer?"
82
+ )
83
+ log_msg_success_processed = f"[TOOL_RESULT_SUCCESS_PROCESSED] Agent_ID: {agent_id}, Tool: {event.tool_name}, Invocation_ID: {tool_invocation_id}, Result (first 200 chars of stringified): {str(event.result)[:200]}"
84
+ if notifier:
85
+ try:
86
+ log_data = {
87
+ "log_entry": log_msg_success_processed,
88
+ "tool_invocation_id": tool_invocation_id,
89
+ "tool_name": event.tool_name,
90
+ }
91
+ notifier.notify_agent_data_tool_log(log_data)
92
+ except Exception as e_notify:
93
+ logger.error(f"Agent '{agent_id}': Error notifying tool result success log: {e_notify}", exc_info=True)
94
+
95
+ logger.debug(f"Agent '{agent_id}' preparing message for LLM based on tool '{event.tool_name}' (ID: {tool_invocation_id}) result:\n---\n{content_for_llm}\n---")
96
+ llm_user_message = LLMUserMessage(content=content_for_llm)
97
+
98
+ next_event = LLMUserMessageReadyEvent(llm_user_message=llm_user_message)
99
+ await context.input_event_queues.enqueue_internal_system_event(next_event)
100
+
101
+ logger.info(f"Agent '{agent_id}' enqueued LLMUserMessageReadyEvent for LLM based on tool '{event.tool_name}' (ID: {tool_invocation_id}) result summary.")
@@ -0,0 +1,77 @@
1
+ # file: autobyteus/autobyteus/agent/handlers/user_input_message_event_handler.py
2
+ import logging
3
+ from typing import TYPE_CHECKING
4
+
5
+ from autobyteus.agent.handlers.base_event_handler import AgentEventHandler
6
+ from autobyteus.agent.events import UserMessageReceivedEvent, LLMUserMessageReadyEvent
7
+ from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
8
+ from autobyteus.agent.input_processor import BaseAgentUserInputMessageProcessor
9
+ from autobyteus.llm.user_message import LLMUserMessage
10
+
11
+
12
+ if TYPE_CHECKING:
13
+ from autobyteus.agent.context import AgentContext
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class UserInputMessageEventHandler(AgentEventHandler):
18
+ """
19
+ Handles UserMessageReceivedEvents by first applying any configured
20
+ AgentUserInputMessageProcessors (provided as instances) to the AgentInputUserMessage,
21
+ then converting the processed message into an LLMUserMessage, and finally
22
+ enqueuing an LLMUserMessageReadyEvent for further processing by the LLM.
23
+ """
24
+
25
+ def __init__(self):
26
+ logger.info("UserInputMessageEventHandler initialized.")
27
+
28
+ async def handle(self,
29
+ event: UserMessageReceivedEvent,
30
+ context: 'AgentContext') -> None:
31
+ if not isinstance(event, UserMessageReceivedEvent):
32
+ logger.warning(f"UserInputMessageEventHandler received non-UserMessageReceivedEvent: {type(event)}. Skipping.")
33
+ return
34
+
35
+ original_agent_input_user_msg: AgentInputUserMessage = event.agent_input_user_message
36
+ processed_agent_input_user_msg: AgentInputUserMessage = original_agent_input_user_msg
37
+
38
+ logger.info(f"Agent '{context.agent_id}' handling UserMessageReceivedEvent: '{original_agent_input_user_msg.content[:100]}...'")
39
+
40
+ processor_instances = context.config.input_processors
41
+ if processor_instances:
42
+ processor_names = [p.get_name() for p in processor_instances]
43
+ logger.debug(f"Agent '{context.agent_id}': Applying input processors: {processor_names}")
44
+ for processor_instance in processor_instances:
45
+ processor_name_for_log = "unknown"
46
+ try:
47
+ if not isinstance(processor_instance, BaseAgentUserInputMessageProcessor):
48
+ logger.error(f"Agent '{context.agent_id}': Invalid input processor type in config: {type(processor_instance)}. Skipping.")
49
+ continue
50
+
51
+ processor_name_for_log = processor_instance.get_name()
52
+ logger.debug(f"Agent '{context.agent_id}': Applying input processor '{processor_name_for_log}'.")
53
+ msg_before_this_processor = processed_agent_input_user_msg
54
+ # Pass the original event to the processor
55
+ processed_agent_input_user_msg = await processor_instance.process(
56
+ message=msg_before_this_processor,
57
+ context=context,
58
+ triggering_event=event
59
+ )
60
+ logger.info(f"Agent '{context.agent_id}': Input processor '{processor_name_for_log}' applied successfully.")
61
+
62
+ except Exception as e:
63
+ logger.error(f"Agent '{context.agent_id}': Error applying input processor '{processor_name_for_log}': {e}. "
64
+ f"Skipping this processor and continuing with message from before this processor.", exc_info=True)
65
+ processed_agent_input_user_msg = msg_before_this_processor
66
+ else:
67
+ logger.debug(f"Agent '{context.agent_id}': No input processors configured in agent config.")
68
+
69
+ llm_user_message = LLMUserMessage(
70
+ content=processed_agent_input_user_msg.content,
71
+ image_urls=processed_agent_input_user_msg.image_urls
72
+ )
73
+
74
+ llm_user_message_ready_event = LLMUserMessageReadyEvent(llm_user_message=llm_user_message)
75
+ await context.input_event_queues.enqueue_internal_system_event(llm_user_message_ready_event)
76
+
77
+ logger.info(f"Agent '{context.agent_id}' processed AgentInputUserMessage and enqueued LLMUserMessageReadyEvent.")
@@ -0,0 +1,16 @@
1
+ # file: autobyteus/autobyteus/agent/hooks/__init__.py
2
+ """
3
+ Components for defining and running lifecycle hooks based on agent phase transitions.
4
+ """
5
+ from .base_phase_hook import BasePhaseHook
6
+ from .hook_definition import PhaseHookDefinition
7
+ from .hook_meta import PhaseHookMeta
8
+ from .hook_registry import PhaseHookRegistry, default_phase_hook_registry
9
+
10
+ __all__ = [
11
+ "BasePhaseHook",
12
+ "PhaseHookDefinition",
13
+ "PhaseHookMeta",
14
+ "PhaseHookRegistry",
15
+ "default_phase_hook_registry",
16
+ ]
@@ -0,0 +1,61 @@
1
+ # file: autobyteus/autobyteus/agent/hooks/base_phase_hook.py
2
+ import logging
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING
5
+
6
+ from autobyteus.agent.phases import AgentOperationalPhase
7
+ from .hook_meta import PhaseHookMeta
8
+
9
+ if TYPE_CHECKING:
10
+ from autobyteus.agent.context import AgentContext
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class BasePhaseHook(ABC, metaclass=PhaseHookMeta):
15
+ """
16
+ Abstract base class for creating hooks that execute on specific agent
17
+ phase transitions.
18
+
19
+ Subclasses must define the `source_phase` and `target_phase` to specify
20
+ the exact transition they are interested in, and implement the `execute`
21
+ method for their custom logic.
22
+ """
23
+
24
+ @classmethod
25
+ def get_name(cls) -> str:
26
+ """
27
+ Returns the unique registration name for this hook.
28
+ Defaults to the class name. Can be overridden by subclasses.
29
+ """
30
+ return cls.__name__
31
+
32
+ @property
33
+ @abstractmethod
34
+ def source_phase(self) -> AgentOperationalPhase:
35
+ """The source phase for the transition this hook targets."""
36
+ raise NotImplementedError
37
+
38
+ @property
39
+ @abstractmethod
40
+ def target_phase(self) -> AgentOperationalPhase:
41
+ """The target phase for the transition this hook targets."""
42
+ raise NotImplementedError
43
+
44
+ @abstractmethod
45
+ async def execute(self, context: 'AgentContext') -> None:
46
+ """
47
+ The method executed when the specified phase transition occurs.
48
+
49
+ Args:
50
+ context: The agent's context at the time of the transition.
51
+ """
52
+ raise NotImplementedError
53
+
54
+ def __repr__(self) -> str:
55
+ # Use try-except in case properties are not yet implemented during introspection
56
+ try:
57
+ return (f"<{self.__class__.__name__} "
58
+ f"source='{self.source_phase.value}' "
59
+ f"target='{self.target_phase.value}'>")
60
+ except (NotImplementedError, AttributeError):
61
+ return f"<{self.__class__.__name__} (unconfigured)>"
@@ -0,0 +1,36 @@
1
+ # file: autobyteus/autobyteus/agent/hooks/hook_definition.py
2
+ import logging
3
+ from typing import Type, TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from .base_phase_hook import BasePhaseHook
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class PhaseHookDefinition:
11
+ """
12
+ Represents the definition of a phase hook.
13
+ Contains its registered name and the class itself.
14
+ """
15
+ def __init__(self, name: str, hook_class: Type['BasePhaseHook']):
16
+ """
17
+ Initializes the PhaseHookDefinition.
18
+
19
+ Args:
20
+ name: The unique registered name of the hook.
21
+ hook_class: The class of the phase hook.
22
+
23
+ Raises:
24
+ ValueError: If name is empty or hook_class is not a type.
25
+ """
26
+ if not name or not isinstance(name, str):
27
+ raise ValueError("Hook name must be a non-empty string.")
28
+ if not isinstance(hook_class, type):
29
+ raise ValueError("hook_class must be a class type.")
30
+
31
+ self.name: str = name
32
+ self.hook_class: Type['BasePhaseHook'] = hook_class
33
+ logger.debug(f"PhaseHookDefinition created: name='{name}', class='{hook_class.__name__}'.")
34
+
35
+ def __repr__(self) -> str:
36
+ return f"<PhaseHookDefinition name='{self.name}', class='{self.hook_class.__name__}'>"
@@ -0,0 +1,37 @@
1
+ # file: autobyteus/autobyteus/agent/hooks/hook_meta.py
2
+ import logging
3
+ from abc import ABCMeta
4
+
5
+ from .hook_registry import default_phase_hook_registry
6
+ from .hook_definition import PhaseHookDefinition
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class PhaseHookMeta(ABCMeta):
11
+ """
12
+ Metaclass for BasePhaseHook that automatically registers concrete
13
+ hook subclasses with the default_phase_hook_registry.
14
+ Registration uses the name obtained from the class method `get_name()`.
15
+ """
16
+ def __init__(cls, name, bases, dct):
17
+ super().__init__(name, bases, dct)
18
+
19
+ if name == 'BasePhaseHook' or getattr(cls, "__abstractmethods__", None):
20
+ logger.debug(f"Skipping registration for abstract phase hook class: {name}")
21
+ return
22
+
23
+ try:
24
+ hook_name = cls.get_name()
25
+
26
+ if not hook_name or not isinstance(hook_name, str):
27
+ logger.error(f"Phase hook class {name} must return a valid string from static get_name(). Skipping registration.")
28
+ return
29
+
30
+ definition = PhaseHookDefinition(name=hook_name, hook_class=cls)
31
+ default_phase_hook_registry.register_hook(definition)
32
+ logger.info(f"Auto-registered phase hook: '{hook_name}' from class {name} (no schema).")
33
+
34
+ except AttributeError as e:
35
+ logger.error(f"Phase hook class {name} is missing required static/class method 'get_name' ({e}). Skipping registration.")
36
+ except Exception as e:
37
+ logger.error(f"Failed to auto-register phase hook class {name}: {e}", exc_info=True)
@@ -0,0 +1,118 @@
1
+ # file: autobyteus/autobyteus/agent/hooks/hook_registry.py
2
+ import logging
3
+ from typing import TYPE_CHECKING, Dict, List, Optional
4
+
5
+ from autobyteus.utils.singleton import SingletonMeta
6
+ from .hook_definition import PhaseHookDefinition
7
+
8
+ if TYPE_CHECKING:
9
+ from .base_phase_hook import BasePhaseHook
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class PhaseHookRegistry(metaclass=SingletonMeta):
14
+ """
15
+ A singleton registry for PhaseHookDefinition objects.
16
+ Hooks are typically auto-registered via PhaseHookMeta.
17
+ """
18
+
19
+ def __init__(self):
20
+ """Initializes the PhaseHookRegistry with an empty store."""
21
+ self._definitions: Dict[str, PhaseHookDefinition] = {}
22
+ logger.info("PhaseHookRegistry initialized.")
23
+
24
+ def register_hook(self, definition: PhaseHookDefinition) -> None:
25
+ """
26
+ Registers a phase hook definition.
27
+ If a definition with the same name already exists, it will be overwritten,
28
+ and a warning will be logged.
29
+
30
+ Args:
31
+ definition: The PhaseHookDefinition object to register.
32
+
33
+ Raises:
34
+ TypeError: If the definition is not an instance of PhaseHookDefinition.
35
+ """
36
+ if not isinstance(definition, PhaseHookDefinition):
37
+ raise TypeError(f"Expected PhaseHookDefinition instance, got {type(definition).__name__}.")
38
+
39
+ hook_name = definition.name
40
+ if hook_name in self._definitions:
41
+ logger.warning(f"Overwriting existing phase hook definition for name: '{hook_name}'.")
42
+
43
+ self._definitions[hook_name] = definition
44
+ logger.info(f"Phase hook definition '{hook_name}' (class: '{definition.hook_class.__name__}') registered successfully.")
45
+
46
+ def get_hook_definition(self, name: str) -> Optional[PhaseHookDefinition]:
47
+ """
48
+ Retrieves a phase hook definition by its name.
49
+
50
+ Args:
51
+ name: The name of the phase hook definition to retrieve.
52
+
53
+ Returns:
54
+ The PhaseHookDefinition object if found, otherwise None.
55
+ """
56
+ if not isinstance(name, str):
57
+ logger.warning(f"Attempted to retrieve hook definition with non-string name: {type(name).__name__}.")
58
+ return None
59
+ definition = self._definitions.get(name)
60
+ if not definition:
61
+ logger.debug(f"Phase hook definition with name '{name}' not found in registry.")
62
+ return definition
63
+
64
+ def get_hook(self, name: str) -> Optional['BasePhaseHook']:
65
+ """
66
+ Retrieves an instance of a phase hook by its name.
67
+
68
+ Args:
69
+ name: The name of the phase hook to retrieve.
70
+
71
+ Returns:
72
+ An instance of the BasePhaseHook if found and instantiable, otherwise None.
73
+ """
74
+ definition = self.get_hook_definition(name)
75
+ if definition:
76
+ try:
77
+ return definition.hook_class()
78
+ except Exception as e:
79
+ logger.error(f"Failed to instantiate phase hook '{name}' from class '{definition.hook_class.__name__}': {e}", exc_info=True)
80
+ return None
81
+ return None
82
+
83
+ def list_hook_names(self) -> List[str]:
84
+ """
85
+ Returns a list of names of all registered phase hook definitions.
86
+
87
+ Returns:
88
+ A list of strings, where each string is a registered hook name.
89
+ """
90
+ return list(self._definitions.keys())
91
+
92
+ def get_all_definitions(self) -> Dict[str, PhaseHookDefinition]:
93
+ """
94
+ Returns a shallow copy of the dictionary containing all registered phase hook definitions.
95
+
96
+ Returns:
97
+ A dictionary where keys are hook names and values are PhaseHookDefinition objects.
98
+ """
99
+ return dict(self._definitions)
100
+
101
+ def clear(self) -> None:
102
+ """Removes all definitions from the registry."""
103
+ count = len(self._definitions)
104
+ self._definitions.clear()
105
+ logger.info(f"Cleared {count} definitions from the PhaseHookRegistry.")
106
+
107
+ def __len__(self) -> int:
108
+ """Returns the number of registered hook definitions."""
109
+ return len(self._definitions)
110
+
111
+ def __contains__(self, name: str) -> bool:
112
+ """Checks if a hook definition is in the registry by name."""
113
+ if isinstance(name, str):
114
+ return name in self._definitions
115
+ return False
116
+
117
+ # Default instance of the registry
118
+ default_phase_hook_registry = PhaseHookRegistry()