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.
- autobyteus/agent/agent.py +97 -222
- autobyteus/agent/bootstrap_steps/__init__.py +19 -0
- autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +88 -0
- autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +57 -0
- autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +38 -0
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +93 -0
- autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +47 -0
- autobyteus/agent/context/__init__.py +13 -0
- autobyteus/agent/context/agent_config.py +84 -0
- autobyteus/agent/context/agent_context.py +129 -0
- autobyteus/agent/context/agent_phase_manager.py +264 -0
- autobyteus/agent/context/agent_runtime_state.py +89 -0
- autobyteus/agent/context/phases.py +49 -0
- autobyteus/agent/events/__init__.py +52 -0
- autobyteus/agent/events/agent_events.py +110 -0
- autobyteus/agent/events/agent_input_event_queue_manager.py +174 -0
- autobyteus/agent/events/notifiers.py +122 -0
- autobyteus/agent/events/worker_event_dispatcher.py +118 -0
- autobyteus/agent/factory/__init__.py +9 -0
- autobyteus/agent/factory/agent_factory.py +145 -0
- autobyteus/agent/group/agent_group.py +164 -0
- autobyteus/agent/group/agent_group_context.py +81 -0
- autobyteus/agent/handlers/__init__.py +36 -0
- autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +148 -0
- autobyteus/agent/handlers/base_event_handler.py +36 -0
- autobyteus/agent/handlers/event_handler_registry.py +76 -0
- autobyteus/agent/handlers/generic_event_handler.py +46 -0
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +76 -0
- autobyteus/agent/handlers/lifecycle_event_logger.py +64 -0
- autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +138 -0
- autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +140 -0
- autobyteus/agent/handlers/tool_execution_approval_event_handler.py +85 -0
- autobyteus/agent/handlers/tool_invocation_request_event_handler.py +211 -0
- autobyteus/agent/handlers/tool_result_event_handler.py +101 -0
- autobyteus/agent/handlers/user_input_message_event_handler.py +77 -0
- autobyteus/agent/hooks/__init__.py +16 -0
- autobyteus/agent/hooks/base_phase_hook.py +61 -0
- autobyteus/agent/hooks/hook_definition.py +36 -0
- autobyteus/agent/hooks/hook_meta.py +37 -0
- autobyteus/agent/hooks/hook_registry.py +118 -0
- autobyteus/agent/input_processor/__init__.py +18 -0
- autobyteus/agent/input_processor/base_user_input_processor.py +54 -0
- autobyteus/agent/input_processor/content_prefixing_input_processor.py +41 -0
- autobyteus/agent/input_processor/metadata_appending_input_processor.py +34 -0
- autobyteus/agent/input_processor/passthrough_input_processor.py +33 -0
- autobyteus/agent/input_processor/processor_definition.py +42 -0
- autobyteus/agent/input_processor/processor_meta.py +46 -0
- autobyteus/agent/input_processor/processor_registry.py +117 -0
- autobyteus/agent/llm_response_processor/__init__.py +16 -0
- autobyteus/agent/llm_response_processor/base_processor.py +53 -0
- autobyteus/agent/llm_response_processor/processor_definition.py +36 -0
- autobyteus/agent/llm_response_processor/processor_meta.py +37 -0
- autobyteus/agent/llm_response_processor/processor_registry.py +113 -0
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +54 -0
- autobyteus/agent/message/__init__.py +20 -0
- autobyteus/agent/message/agent_input_user_message.py +96 -0
- autobyteus/agent/message/context_file.py +82 -0
- autobyteus/agent/message/context_file_type.py +63 -0
- autobyteus/agent/message/{message.py → inter_agent_message.py} +12 -12
- autobyteus/agent/message/{message_types.py → inter_agent_message_type.py} +8 -6
- autobyteus/agent/message/send_message_to.py +142 -36
- autobyteus/agent/phases/__init__.py +18 -0
- autobyteus/agent/phases/discover.py +52 -0
- autobyteus/agent/phases/manager.py +265 -0
- autobyteus/agent/phases/phase_enum.py +49 -0
- autobyteus/agent/phases/transition_decorator.py +40 -0
- autobyteus/agent/phases/transition_info.py +33 -0
- autobyteus/agent/remote_agent.py +244 -0
- autobyteus/agent/runtime/__init__.py +15 -0
- autobyteus/agent/runtime/agent_runtime.py +137 -0
- autobyteus/agent/runtime/agent_thread_pool_manager.py +88 -0
- autobyteus/agent/runtime/agent_worker.py +200 -0
- autobyteus/agent/streaming/__init__.py +15 -0
- autobyteus/agent/streaming/agent_event_stream.py +173 -0
- autobyteus/agent/streaming/queue_streamer.py +58 -0
- autobyteus/agent/streaming/stream_event_payloads.py +167 -0
- autobyteus/agent/streaming/stream_events.py +126 -0
- autobyteus/agent/system_prompt_processor/__init__.py +14 -0
- autobyteus/agent/system_prompt_processor/base_processor.py +48 -0
- autobyteus/agent/system_prompt_processor/processor_definition.py +40 -0
- autobyteus/agent/system_prompt_processor/processor_meta.py +47 -0
- autobyteus/agent/system_prompt_processor/processor_registry.py +119 -0
- autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +79 -0
- autobyteus/agent/tool_invocation.py +54 -5
- autobyteus/agent/utils/__init__.py +9 -0
- autobyteus/agent/utils/wait_for_idle.py +59 -0
- autobyteus/agent/workflow/__init__.py +11 -0
- autobyteus/agent/workflow/agentic_workflow.py +89 -0
- autobyteus/agent/workflow/base_agentic_workflow.py +98 -0
- autobyteus/agent/workspace/__init__.py +11 -0
- autobyteus/agent/workspace/base_workspace.py +77 -0
- autobyteus/agent/workspace/workspace_config.py +160 -0
- autobyteus/agent/workspace/workspace_definition.py +36 -0
- autobyteus/agent/workspace/workspace_meta.py +37 -0
- autobyteus/agent/workspace/workspace_registry.py +72 -0
- autobyteus/cli/__init__.py +11 -0
- autobyteus/cli/agent_cli.py +117 -0
- autobyteus/cli/cli_display.py +205 -0
- autobyteus/events/event_emitter.py +33 -56
- autobyteus/events/event_manager.py +134 -66
- autobyteus/events/event_types.py +43 -14
- autobyteus/llm/api/autobyteus_llm.py +13 -25
- autobyteus/llm/api/bedrock_llm.py +9 -3
- autobyteus/llm/api/claude_llm.py +10 -5
- autobyteus/llm/api/deepseek_llm.py +55 -93
- autobyteus/llm/api/gemini_llm.py +10 -4
- autobyteus/llm/api/grok_llm.py +55 -79
- autobyteus/llm/api/groq_llm.py +10 -5
- autobyteus/llm/api/mistral_llm.py +13 -8
- autobyteus/llm/api/nvidia_llm.py +9 -4
- autobyteus/llm/api/ollama_llm.py +58 -50
- autobyteus/llm/api/openai_llm.py +20 -14
- autobyteus/llm/base_llm.py +95 -34
- autobyteus/llm/extensions/base_extension.py +3 -4
- autobyteus/llm/extensions/token_usage_tracking_extension.py +13 -4
- autobyteus/llm/llm_factory.py +116 -53
- autobyteus/llm/models.py +92 -17
- autobyteus/llm/ollama_provider.py +6 -2
- autobyteus/llm/ollama_provider_resolver.py +44 -0
- autobyteus/llm/user_message.py +73 -0
- autobyteus/llm/utils/llm_config.py +124 -27
- autobyteus/llm/utils/response_types.py +3 -2
- autobyteus/llm/utils/token_usage.py +7 -4
- autobyteus/rpc/__init__.py +73 -0
- autobyteus/rpc/client/__init__.py +17 -0
- autobyteus/rpc/client/abstract_client_connection.py +124 -0
- autobyteus/rpc/client/client_connection_manager.py +153 -0
- autobyteus/rpc/client/sse_client_connection.py +306 -0
- autobyteus/rpc/client/stdio_client_connection.py +280 -0
- autobyteus/rpc/config/__init__.py +13 -0
- autobyteus/rpc/config/agent_server_config.py +153 -0
- autobyteus/rpc/config/agent_server_registry.py +152 -0
- autobyteus/rpc/hosting.py +244 -0
- autobyteus/rpc/protocol.py +244 -0
- autobyteus/rpc/server/__init__.py +20 -0
- autobyteus/rpc/server/agent_server_endpoint.py +181 -0
- autobyteus/rpc/server/base_method_handler.py +40 -0
- autobyteus/rpc/server/method_handlers.py +259 -0
- autobyteus/rpc/server/sse_server_handler.py +182 -0
- autobyteus/rpc/server/stdio_server_handler.py +151 -0
- autobyteus/rpc/server_main.py +198 -0
- autobyteus/rpc/transport_type.py +13 -0
- autobyteus/tools/__init__.py +77 -0
- autobyteus/tools/ask_user_input.py +34 -77
- autobyteus/tools/base_tool.py +73 -38
- autobyteus/tools/bash/__init__.py +2 -0
- autobyteus/tools/bash/bash_executor.py +42 -79
- autobyteus/tools/browser/__init__.py +2 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +50 -42
- autobyteus/tools/browser/session_aware/browser_session_aware_tool.py +7 -4
- autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +117 -125
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +75 -22
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +94 -28
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_web_element_trigger_factory.py +10 -2
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_reader_factory.py +18 -2
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_screenshot_taker_factory.py +10 -2
- autobyteus/tools/browser/session_aware/shared_browser_session_manager.py +4 -3
- autobyteus/tools/browser/standalone/__init__.py +7 -0
- autobyteus/tools/browser/standalone/factory/google_search_factory.py +17 -2
- autobyteus/tools/browser/standalone/factory/webpage_reader_factory.py +17 -2
- autobyteus/tools/browser/standalone/factory/webpage_screenshot_taker_factory.py +10 -2
- autobyteus/tools/browser/standalone/google_search_ui.py +104 -67
- autobyteus/tools/browser/standalone/navigate_to.py +52 -28
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +94 -0
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +146 -61
- autobyteus/tools/browser/standalone/webpage_reader.py +80 -61
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +91 -45
- autobyteus/tools/factory/__init__.py +9 -0
- autobyteus/tools/factory/tool_factory.py +25 -4
- autobyteus/tools/file/file_reader.py +22 -51
- autobyteus/tools/file/file_writer.py +25 -56
- autobyteus/tools/functional_tool.py +249 -0
- autobyteus/tools/image_downloader.py +49 -71
- autobyteus/tools/mcp/__init__.py +47 -0
- autobyteus/tools/mcp/call_handlers/__init__.py +18 -0
- autobyteus/tools/mcp/call_handlers/base_handler.py +40 -0
- autobyteus/tools/mcp/call_handlers/sse_handler.py +22 -0
- autobyteus/tools/mcp/call_handlers/stdio_handler.py +76 -0
- autobyteus/tools/mcp/call_handlers/streamable_http_handler.py +55 -0
- autobyteus/tools/mcp/config_service.py +237 -0
- autobyteus/tools/mcp/factory.py +70 -0
- autobyteus/tools/mcp/registrar.py +323 -0
- autobyteus/tools/mcp/schema_mapper.py +131 -0
- autobyteus/tools/mcp/tool.py +101 -0
- autobyteus/tools/mcp/types.py +98 -0
- autobyteus/tools/parameter_schema.py +268 -0
- autobyteus/tools/pdf_downloader.py +78 -79
- autobyteus/tools/registry/__init__.py +0 -2
- autobyteus/tools/registry/tool_definition.py +113 -34
- autobyteus/tools/registry/tool_registry.py +64 -22
- autobyteus/tools/timer.py +150 -102
- autobyteus/tools/tool_category.py +11 -0
- autobyteus/tools/tool_config.py +117 -0
- autobyteus/tools/tool_meta.py +50 -26
- autobyteus/tools/tool_state.py +20 -0
- autobyteus/tools/usage/__init__.py +6 -0
- autobyteus/tools/usage/formatters/__init__.py +31 -0
- autobyteus/tools/usage/formatters/anthropic_json_example_formatter.py +18 -0
- autobyteus/tools/usage/formatters/anthropic_json_schema_formatter.py +25 -0
- autobyteus/tools/usage/formatters/base_formatter.py +42 -0
- autobyteus/tools/usage/formatters/default_json_example_formatter.py +42 -0
- autobyteus/tools/usage/formatters/default_json_schema_formatter.py +28 -0
- autobyteus/tools/usage/formatters/default_xml_example_formatter.py +55 -0
- autobyteus/tools/usage/formatters/default_xml_schema_formatter.py +46 -0
- autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +34 -0
- autobyteus/tools/usage/formatters/gemini_json_schema_formatter.py +25 -0
- autobyteus/tools/usage/formatters/google_json_example_formatter.py +34 -0
- autobyteus/tools/usage/formatters/google_json_schema_formatter.py +25 -0
- autobyteus/tools/usage/formatters/openai_json_example_formatter.py +49 -0
- autobyteus/tools/usage/formatters/openai_json_schema_formatter.py +28 -0
- autobyteus/tools/usage/parsers/__init__.py +22 -0
- autobyteus/tools/usage/parsers/anthropic_xml_tool_usage_parser.py +10 -0
- autobyteus/tools/usage/parsers/base_parser.py +41 -0
- autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +106 -0
- autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +136 -0
- autobyteus/tools/usage/parsers/exceptions.py +13 -0
- autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +66 -0
- autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +196 -0
- autobyteus/tools/usage/parsers/provider_aware_tool_usage_parser.py +67 -0
- autobyteus/tools/usage/providers/__init__.py +22 -0
- autobyteus/tools/usage/providers/json_example_provider.py +32 -0
- autobyteus/tools/usage/providers/json_schema_provider.py +35 -0
- autobyteus/tools/usage/providers/json_tool_usage_parser_provider.py +28 -0
- autobyteus/tools/usage/providers/tool_manifest_provider.py +68 -0
- autobyteus/tools/usage/providers/xml_example_provider.py +28 -0
- autobyteus/tools/usage/providers/xml_schema_provider.py +29 -0
- autobyteus/tools/usage/providers/xml_tool_usage_parser_provider.py +26 -0
- autobyteus/tools/usage/registries/__init__.py +20 -0
- autobyteus/tools/usage/registries/json_example_formatter_registry.py +51 -0
- autobyteus/tools/usage/registries/json_schema_formatter_registry.py +51 -0
- autobyteus/tools/usage/registries/json_tool_usage_parser_registry.py +42 -0
- autobyteus/tools/usage/registries/xml_example_formatter_registry.py +30 -0
- autobyteus/tools/usage/registries/xml_schema_formatter_registry.py +33 -0
- autobyteus/tools/usage/registries/xml_tool_usage_parser_registry.py +30 -0
- {autobyteus-1.0.6.dist-info → autobyteus-1.1.1.dist-info}/METADATA +23 -5
- autobyteus-1.1.1.dist-info/RECORD +296 -0
- {autobyteus-1.0.6.dist-info → autobyteus-1.1.1.dist-info}/WHEEL +1 -1
- autobyteus/agent/async_agent.py +0 -175
- autobyteus/agent/async_group_aware_agent.py +0 -136
- autobyteus/agent/group/async_group_aware_agent.py +0 -122
- autobyteus/agent/group/coordinator_agent.py +0 -36
- autobyteus/agent/group/group_aware_agent.py +0 -121
- autobyteus/agent/orchestrator/__init__.py +0 -0
- autobyteus/agent/orchestrator/base_agent_orchestrator.py +0 -82
- autobyteus/agent/orchestrator/multi_replica_agent_orchestrator.py +0 -72
- autobyteus/agent/orchestrator/single_replica_agent_orchestrator.py +0 -43
- autobyteus/agent/response_parser/__init__.py +0 -0
- autobyteus/agent/response_parser/tool_usage_command_parser.py +0 -100
- autobyteus/agent/status.py +0 -12
- autobyteus/conversation/__init__.py +0 -0
- autobyteus/conversation/conversation.py +0 -54
- autobyteus/conversation/user_message.py +0 -59
- autobyteus/events/decorators.py +0 -29
- autobyteus/prompt/prompt_version_manager.py +0 -58
- autobyteus/prompt/storage/__init__.py +0 -0
- autobyteus/prompt/storage/prompt_version_model.py +0 -29
- autobyteus/prompt/storage/prompt_version_repository.py +0 -83
- autobyteus/tools/bash/factory/__init__.py +0 -0
- autobyteus/tools/bash/factory/bash_executor_factory.py +0 -6
- autobyteus/tools/factory/ask_user_input_factory.py +0 -6
- autobyteus/tools/factory/image_downloader_factory.py +0 -9
- autobyteus/tools/factory/pdf_downloader_factory.py +0 -9
- autobyteus/tools/factory/webpage_image_downloader_factory.py +0 -6
- autobyteus/tools/file/factory/__init__.py +0 -0
- autobyteus/tools/file/factory/file_reader_factory.py +0 -6
- autobyteus/tools/file/factory/file_writer_factory.py +0 -6
- autobyteus/tools/web_page_pdf_generator.py +0 -90
- autobyteus-1.0.6.dist-info/RECORD +0 -157
- {autobyteus-1.0.6.dist-info → autobyteus-1.1.1.dist-info}/licenses/LICENSE +0 -0
- {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()
|