autobyteus 1.1.0__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 (95) hide show
  1. autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +1 -1
  2. autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +1 -1
  3. autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +1 -1
  4. autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +1 -1
  5. autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +1 -1
  6. autobyteus/agent/context/__init__.py +0 -5
  7. autobyteus/agent/context/agent_config.py +6 -2
  8. autobyteus/agent/context/agent_context.py +2 -5
  9. autobyteus/agent/context/agent_phase_manager.py +105 -5
  10. autobyteus/agent/context/agent_runtime_state.py +2 -2
  11. autobyteus/agent/context/phases.py +2 -0
  12. autobyteus/agent/events/__init__.py +0 -11
  13. autobyteus/agent/events/agent_events.py +0 -37
  14. autobyteus/agent/events/notifiers.py +25 -7
  15. autobyteus/agent/events/worker_event_dispatcher.py +1 -1
  16. autobyteus/agent/factory/agent_factory.py +6 -2
  17. autobyteus/agent/group/agent_group.py +16 -7
  18. autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +28 -14
  19. autobyteus/agent/handlers/lifecycle_event_logger.py +1 -1
  20. autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +4 -2
  21. autobyteus/agent/handlers/tool_invocation_request_event_handler.py +40 -15
  22. autobyteus/agent/handlers/tool_result_event_handler.py +12 -7
  23. autobyteus/agent/hooks/__init__.py +7 -0
  24. autobyteus/agent/hooks/base_phase_hook.py +11 -2
  25. autobyteus/agent/hooks/hook_definition.py +36 -0
  26. autobyteus/agent/hooks/hook_meta.py +37 -0
  27. autobyteus/agent/hooks/hook_registry.py +118 -0
  28. autobyteus/agent/input_processor/base_user_input_processor.py +6 -3
  29. autobyteus/agent/input_processor/passthrough_input_processor.py +2 -1
  30. autobyteus/agent/input_processor/processor_meta.py +1 -1
  31. autobyteus/agent/input_processor/processor_registry.py +19 -0
  32. autobyteus/agent/llm_response_processor/base_processor.py +6 -3
  33. autobyteus/agent/llm_response_processor/processor_meta.py +1 -1
  34. autobyteus/agent/llm_response_processor/processor_registry.py +19 -0
  35. autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +2 -1
  36. autobyteus/agent/message/context_file_type.py +2 -3
  37. autobyteus/agent/phases/__init__.py +18 -0
  38. autobyteus/agent/phases/discover.py +52 -0
  39. autobyteus/agent/phases/manager.py +265 -0
  40. autobyteus/agent/phases/phase_enum.py +49 -0
  41. autobyteus/agent/phases/transition_decorator.py +40 -0
  42. autobyteus/agent/phases/transition_info.py +33 -0
  43. autobyteus/agent/remote_agent.py +1 -1
  44. autobyteus/agent/runtime/agent_runtime.py +4 -6
  45. autobyteus/agent/runtime/agent_worker.py +1 -1
  46. autobyteus/agent/streaming/agent_event_stream.py +58 -5
  47. autobyteus/agent/streaming/stream_event_payloads.py +24 -13
  48. autobyteus/agent/streaming/stream_events.py +14 -11
  49. autobyteus/agent/system_prompt_processor/base_processor.py +6 -3
  50. autobyteus/agent/system_prompt_processor/processor_meta.py +1 -1
  51. autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +45 -31
  52. autobyteus/agent/tool_invocation.py +29 -3
  53. autobyteus/agent/utils/wait_for_idle.py +1 -1
  54. autobyteus/agent/workspace/__init__.py +2 -0
  55. autobyteus/agent/workspace/base_workspace.py +33 -11
  56. autobyteus/agent/workspace/workspace_config.py +160 -0
  57. autobyteus/agent/workspace/workspace_definition.py +36 -0
  58. autobyteus/agent/workspace/workspace_meta.py +37 -0
  59. autobyteus/agent/workspace/workspace_registry.py +72 -0
  60. autobyteus/cli/__init__.py +4 -3
  61. autobyteus/cli/agent_cli.py +25 -207
  62. autobyteus/cli/cli_display.py +205 -0
  63. autobyteus/events/event_manager.py +2 -1
  64. autobyteus/events/event_types.py +3 -1
  65. autobyteus/llm/api/autobyteus_llm.py +2 -12
  66. autobyteus/llm/api/deepseek_llm.py +5 -5
  67. autobyteus/llm/api/grok_llm.py +5 -5
  68. autobyteus/llm/api/mistral_llm.py +4 -4
  69. autobyteus/llm/api/ollama_llm.py +2 -2
  70. autobyteus/llm/extensions/token_usage_tracking_extension.py +11 -1
  71. autobyteus/llm/llm_factory.py +106 -42
  72. autobyteus/llm/models.py +25 -29
  73. autobyteus/llm/ollama_provider.py +6 -2
  74. autobyteus/llm/ollama_provider_resolver.py +44 -0
  75. autobyteus/tools/__init__.py +2 -0
  76. autobyteus/tools/base_tool.py +7 -1
  77. autobyteus/tools/functional_tool.py +20 -5
  78. autobyteus/tools/mcp/call_handlers/stdio_handler.py +15 -1
  79. autobyteus/tools/mcp/config_service.py +106 -127
  80. autobyteus/tools/mcp/registrar.py +247 -59
  81. autobyteus/tools/mcp/types.py +5 -3
  82. autobyteus/tools/registry/tool_definition.py +8 -1
  83. autobyteus/tools/registry/tool_registry.py +18 -0
  84. autobyteus/tools/tool_category.py +11 -0
  85. autobyteus/tools/tool_meta.py +3 -1
  86. autobyteus/tools/tool_state.py +20 -0
  87. autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +3 -3
  88. autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +2 -1
  89. autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +17 -19
  90. autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +126 -77
  91. {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/METADATA +11 -11
  92. {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/RECORD +95 -78
  93. {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/WHEEL +0 -0
  94. {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/licenses/LICENSE +0 -0
  95. {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/top_level.txt +0 -0
@@ -54,7 +54,12 @@ class ApprovedToolInvocationEventHandler(AgentEventHandler):
54
54
 
55
55
  if notifier:
56
56
  try:
57
- notifier.notify_agent_data_tool_log(log_msg_call) # USE RENAMED METHOD
57
+ log_data = {
58
+ "log_entry": log_msg_call,
59
+ "tool_invocation_id": invocation_id,
60
+ "tool_name": tool_name,
61
+ }
62
+ notifier.notify_agent_data_tool_log(log_data)
58
63
  except Exception as e_notify:
59
64
  logger.error(f"Agent '{agent_id}': Error notifying approved tool call log: {e_notify}", exc_info=True)
60
65
 
@@ -71,11 +76,14 @@ class ApprovedToolInvocationEventHandler(AgentEventHandler):
71
76
  "name": tool_name,
72
77
  "content": f"Error: Approved tool '{tool_name}' execution failed. Reason: {error_message}",
73
78
  })
74
- log_msg_error = f"[APPROVED_TOOL_ERROR] Agent_ID: {agent_id}, Tool: {tool_name}, Invocation_ID: {invocation_id}, Error: {error_message}"
79
+ log_msg_error = f"[APPROVED_TOOL_ERROR] {error_message}"
75
80
  if notifier:
76
81
  try:
77
- notifier.notify_agent_data_tool_log(log_msg_error) # USE RENAMED METHOD
78
- notifier.notify_agent_error_output_generation( # USE RENAMED METHOD
82
+ # Log entry
83
+ log_data = { "log_entry": log_msg_error, "tool_invocation_id": invocation_id, "tool_name": tool_name }
84
+ notifier.notify_agent_data_tool_log(log_data)
85
+ # Generic output error
86
+ notifier.notify_agent_error_output_generation(
79
87
  error_source=f"ApprovedToolExecution.ToolNotFound.{tool_name}",
80
88
  error_message=error_message
81
89
  )
@@ -87,11 +95,11 @@ class ApprovedToolInvocationEventHandler(AgentEventHandler):
87
95
  execution_result = await tool_instance.execute(context=context, **arguments)
88
96
 
89
97
  try:
90
- result_str_for_log = json.dumps(execution_result)
91
- except TypeError:
92
- result_str_for_log = str(execution_result)
98
+ result_json_for_log = json.dumps(execution_result)
99
+ except (TypeError, ValueError):
100
+ result_json_for_log = json.dumps(str(execution_result))
93
101
 
94
- logger.info(f"Approved tool '{tool_name}' (ID: {invocation_id}) executed successfully by agent '{agent_id}'. Result: {result_str_for_log[:200]}...")
102
+ logger.info(f"Approved tool '{tool_name}' (ID: {invocation_id}) executed successfully by agent '{agent_id}'.")
95
103
  result_event = ToolResultEvent(tool_name=tool_name, result=execution_result, error=None, tool_invocation_id=invocation_id)
96
104
 
97
105
  history_content = str(execution_result)
@@ -101,15 +109,18 @@ class ApprovedToolInvocationEventHandler(AgentEventHandler):
101
109
  "name": tool_name,
102
110
  "content": history_content,
103
111
  })
104
- log_msg_result = f"[APPROVED_TOOL_RESULT] Agent_ID: {agent_id}, Tool: {tool_name}, Invocation_ID: {invocation_id}, Outcome (first 200 chars): {result_str_for_log[:200]}"
112
+ log_msg_result = f"[APPROVED_TOOL_RESULT] {result_json_for_log}"
105
113
  if notifier:
106
114
  try:
107
- notifier.notify_agent_data_tool_log(log_msg_result) # USE RENAMED METHOD
115
+ # Log entry with embedded JSON result
116
+ log_data = { "log_entry": log_msg_result, "tool_invocation_id": invocation_id, "tool_name": tool_name }
117
+ notifier.notify_agent_data_tool_log(log_data)
108
118
  except Exception as e_notify:
109
119
  logger.error(f"Agent '{agent_id}': Error notifying approved tool result log: {e_notify}", exc_info=True)
110
120
 
111
121
  except Exception as e:
112
122
  error_message = f"Error executing approved tool '{tool_name}' (ID: {invocation_id}): {str(e)}"
123
+ error_details = traceback.format_exc()
113
124
  logger.error(f"Agent '{agent_id}' {error_message}", exc_info=True)
114
125
  result_event = ToolResultEvent(tool_name=tool_name, result=None, error=error_message, tool_invocation_id=invocation_id)
115
126
  context.add_message_to_history({
@@ -118,14 +129,17 @@ class ApprovedToolInvocationEventHandler(AgentEventHandler):
118
129
  "name": tool_name,
119
130
  "content": f"Error: Approved tool '{tool_name}' execution failed. Reason: {error_message}",
120
131
  })
121
- log_msg_exception = f"[APPROVED_TOOL_EXCEPTION] Agent_ID: {agent_id}, Tool: {tool_name}, Invocation_ID: {invocation_id}, Exception: {error_message}"
132
+ log_msg_exception = f"[APPROVED_TOOL_EXCEPTION] {error_message}\nDetails:\n{error_details}"
122
133
  if notifier:
123
134
  try:
124
- notifier.notify_agent_data_tool_log(log_msg_exception) # USE RENAMED METHOD
125
- notifier.notify_agent_error_output_generation( # USE RENAMED METHOD
135
+ # Log entry
136
+ log_data = { "log_entry": log_msg_exception, "tool_invocation_id": invocation_id, "tool_name": tool_name }
137
+ notifier.notify_agent_data_tool_log(log_data)
138
+ # Generic output error
139
+ notifier.notify_agent_error_output_generation(
126
140
  error_source=f"ApprovedToolExecution.Exception.{tool_name}",
127
141
  error_message=error_message,
128
- error_details=traceback.format_exc()
142
+ error_details=error_details
129
143
  )
130
144
  except Exception as e_notify:
131
145
  logger.error(f"Agent '{agent_id}': Error notifying approved tool exception log/output error: {e_notify}", exc_info=True)
@@ -10,7 +10,7 @@ from autobyteus.agent.events import (
10
10
  AgentErrorEvent,
11
11
  LifecycleEvent
12
12
  )
13
- from autobyteus.agent.context.phases import AgentOperationalPhase # Import new phase enum
13
+ from autobyteus.agent.phases import AgentOperationalPhase # Import new phase enum
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from autobyteus.agent.context import AgentContext
@@ -42,7 +42,9 @@ class LLMCompleteResponseReceivedEventHandler(AgentEventHandler):
42
42
  )
43
43
  if complete_response_reasoning:
44
44
  logger.debug(f"Agent '{agent_id}' received LLM reasoning for processing:\n---\n{complete_response_reasoning}\n---")
45
- logger.debug(f"Agent '{agent_id}' received full LLM content for processing:\n---\n{complete_response_text}\n---")
45
+
46
+ # Changed from .debug to .info as per user request
47
+ logger.info(f"Agent '{agent_id}' received full LLM content for processing:\n---\n{complete_response_text}\n---")
46
48
 
47
49
  any_processor_took_action = False
48
50
 
@@ -133,4 +135,4 @@ class LLMCompleteResponseReceivedEventHandler(AgentEventHandler):
133
135
  notifier.notify_agent_data_assistant_complete_response(complete_response)
134
136
  logger.debug(f"Agent '{agent_id}' emitted AGENT_DATA_ASSISTANT_COMPLETE_RESPONSE event.")
135
137
  except Exception as e_notify: # pragma: no cover
136
- logger.error(f"Agent '{agent_id}': Error emitting AGENT_DATA_ASSISTANT_COMPLETE_RESPONSE: {e_notify}", exc_info=True)
138
+ logger.error(f"Agent '{agent_id}': Error emitting AGENT_DATA_ASSISTANT_COMPLETE_RESPONSE: {e_notify}", exc_info=True)
@@ -34,6 +34,17 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
34
34
  tool_name = tool_invocation.name
35
35
  arguments = tool_invocation.arguments
36
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)
37
48
 
38
49
  logger.info(f"Agent '{agent_id}' executing tool directly: '{tool_name}' (ID: {invocation_id}) with args: {arguments}")
39
50
 
@@ -45,7 +56,12 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
45
56
  log_msg_call = f"[TOOL_CALL_DIRECT] Agent_ID: {agent_id}, Tool: {tool_name}, Invocation_ID: {invocation_id}, Arguments: {args_str}"
46
57
  if notifier:
47
58
  try:
48
- notifier.notify_agent_data_tool_log(log_msg_call) # USE RENAMED METHOD
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)
49
65
  except Exception as e_notify:
50
66
  logger.error(f"Agent '{agent_id}': Error notifying tool call log: {e_notify}", exc_info=True)
51
67
 
@@ -62,11 +78,14 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
62
78
  "name": tool_name,
63
79
  "content": f"Error: Tool '{tool_name}' execution failed. Reason: {error_message}",
64
80
  })
65
- log_msg_error = f"[TOOL_ERROR_DIRECT] Agent_ID: {agent_id}, Tool: {tool_name}, Invocation_ID: {invocation_id}, Error: {error_message}"
81
+ log_msg_error = f"[TOOL_ERROR_DIRECT] {error_message}"
66
82
  if notifier:
67
83
  try:
68
- notifier.notify_agent_data_tool_log(log_msg_error) # USE RENAMED METHOD
69
- notifier.notify_agent_error_output_generation( # USE RENAMED METHOD
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(
70
89
  error_source=f"ToolExecutionDirect.ToolNotFound.{tool_name}",
71
90
  error_message=error_message
72
91
  )
@@ -78,11 +97,11 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
78
97
  execution_result = await tool_instance.execute(context=context, **arguments)
79
98
 
80
99
  try:
81
- result_str_for_log = json.dumps(execution_result)
82
- except TypeError:
83
- result_str_for_log = str(execution_result)
100
+ result_json_for_log = json.dumps(execution_result)
101
+ except (TypeError, ValueError):
102
+ result_json_for_log = json.dumps(str(execution_result))
84
103
 
85
- logger.info(f"Tool '{tool_name}' (ID: {invocation_id}) executed by agent '{agent_id}'. Result: {result_str_for_log[:200]}...")
104
+ logger.info(f"Tool '{tool_name}' (ID: {invocation_id}) executed by agent '{agent_id}'.")
86
105
  result_event = ToolResultEvent(tool_name=tool_name, result=execution_result, error=None, tool_invocation_id=invocation_id)
87
106
 
88
107
  history_content = str(execution_result)
@@ -92,15 +111,18 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
92
111
  "name": tool_name,
93
112
  "content": history_content,
94
113
  })
95
- log_msg_result = f"[TOOL_RESULT_DIRECT] Agent_ID: {agent_id}, Tool: {tool_name}, Invocation_ID: {invocation_id}, Outcome (first 200 chars): {result_str_for_log[:200]}"
114
+ log_msg_result = f"[TOOL_RESULT_DIRECT] {result_json_for_log}"
96
115
  if notifier:
97
116
  try:
98
- notifier.notify_agent_data_tool_log(log_msg_result) # USE RENAMED METHOD
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)
99
120
  except Exception as e_notify:
100
121
  logger.error(f"Agent '{agent_id}': Error notifying tool result log: {e_notify}", exc_info=True)
101
122
 
102
123
  except Exception as e:
103
124
  error_message = f"Error executing tool '{tool_name}' (ID: {invocation_id}): {str(e)}"
125
+ error_details = traceback.format_exc()
104
126
  logger.error(f"Agent '{agent_id}' {error_message}", exc_info=True)
105
127
  result_event = ToolResultEvent(tool_name=tool_name, result=None, error=error_message, tool_invocation_id=invocation_id)
106
128
  context.add_message_to_history({
@@ -109,14 +131,17 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
109
131
  "name": tool_name,
110
132
  "content": f"Error: Tool '{tool_name}' execution failed. Reason: {error_message}",
111
133
  })
112
- log_msg_exception = f"[TOOL_EXCEPTION_DIRECT] Agent_ID: {agent_id}, Tool: {tool_name}, Invocation_ID: {invocation_id}, Exception: {error_message}"
134
+ log_msg_exception = f"[TOOL_EXCEPTION_DIRECT] {error_message}\nDetails:\n{error_details}"
113
135
  if notifier:
114
136
  try:
115
- notifier.notify_agent_data_tool_log(log_msg_exception) # USE RENAMED METHOD
116
- notifier.notify_agent_error_output_generation( # USE RENAMED METHOD
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(
117
142
  error_source=f"ToolExecutionDirect.Exception.{tool_name}",
118
143
  error_message=error_message,
119
- error_details=traceback.format_exc()
144
+ error_details=error_details
120
145
  )
121
146
  except Exception as e_notify:
122
147
  logger.error(f"Agent '{agent_id}': Error notifying tool exception log/output error: {e_notify}", exc_info=True)
@@ -176,7 +201,7 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
176
201
  }
177
202
  if notifier:
178
203
  try:
179
- notifier.notify_agent_request_tool_invocation_approval(approval_data) # USE RENAMED METHOD
204
+ notifier.notify_agent_request_tool_invocation_approval(approval_data)
180
205
  logger.debug(f"Agent '{agent_id}': Emitted AGENT_REQUEST_TOOL_INVOCATION_APPROVAL for '{tool_invocation.name}' (ID: {tool_invocation.id}).")
181
206
  except Exception as e_notify:
182
207
  logger.error(f"Agent '{agent_id}': Error emitting AGENT_REQUEST_TOOL_INVOCATION_APPROVAL: {e_notify}", exc_info=True)
@@ -61,7 +61,12 @@ class ToolResultEventHandler(AgentEventHandler):
61
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
62
  if notifier:
63
63
  try:
64
- notifier.notify_agent_data_tool_log(log_msg_error_processed) # USE RENAMED METHOD
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)
65
70
  except Exception as e_notify:
66
71
  logger.error(f"Agent '{agent_id}': Error notifying tool result error log: {e_notify}", exc_info=True)
67
72
  else:
@@ -70,11 +75,6 @@ class ToolResultEventHandler(AgentEventHandler):
70
75
  except TypeError: # pragma: no cover
71
76
  result_str_for_llm = str(event.result)
72
77
 
73
- max_len = 2000
74
- if len(result_str_for_llm) > max_len: # pragma: no cover
75
- original_len = len(str(event.result))
76
- result_str_for_llm = result_str_for_llm[:max_len] + f"... (result truncated, original length {original_len})"
77
-
78
78
  content_for_llm = (
79
79
  f"The tool '{event.tool_name}' (invocation ID: {tool_invocation_id}) has executed.\n"
80
80
  f"Result:\n{result_str_for_llm}\n"
@@ -83,7 +83,12 @@ class ToolResultEventHandler(AgentEventHandler):
83
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
84
  if notifier:
85
85
  try:
86
- notifier.notify_agent_data_tool_log(log_msg_success_processed) # USE RENAMED METHOD
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)
87
92
  except Exception as e_notify:
88
93
  logger.error(f"Agent '{agent_id}': Error notifying tool result success log: {e_notify}", exc_info=True)
89
94
 
@@ -3,7 +3,14 @@
3
3
  Components for defining and running lifecycle hooks based on agent phase transitions.
4
4
  """
5
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
6
9
 
7
10
  __all__ = [
8
11
  "BasePhaseHook",
12
+ "PhaseHookDefinition",
13
+ "PhaseHookMeta",
14
+ "PhaseHookRegistry",
15
+ "default_phase_hook_registry",
9
16
  ]
@@ -3,14 +3,15 @@ import logging
3
3
  from abc import ABC, abstractmethod
4
4
  from typing import TYPE_CHECKING
5
5
 
6
- from autobyteus.agent.context.phases import AgentOperationalPhase
6
+ from autobyteus.agent.phases import AgentOperationalPhase
7
+ from .hook_meta import PhaseHookMeta
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from autobyteus.agent.context import AgentContext
10
11
 
11
12
  logger = logging.getLogger(__name__)
12
13
 
13
- class BasePhaseHook(ABC):
14
+ class BasePhaseHook(ABC, metaclass=PhaseHookMeta):
14
15
  """
15
16
  Abstract base class for creating hooks that execute on specific agent
16
17
  phase transitions.
@@ -20,6 +21,14 @@ class BasePhaseHook(ABC):
20
21
  method for their custom logic.
21
22
  """
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
+
23
32
  @property
24
33
  @abstractmethod
25
34
  def source_phase(self) -> AgentOperationalPhase:
@@ -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()
@@ -3,6 +3,8 @@ import logging
3
3
  from abc import ABC, abstractmethod
4
4
  from typing import TYPE_CHECKING, Optional
5
5
 
6
+ from .processor_meta import AgentUserInputMessageProcessorMeta
7
+
6
8
  if TYPE_CHECKING:
7
9
  from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
8
10
  from autobyteus.agent.context import AgentContext # Composite AgentContext
@@ -10,7 +12,7 @@ if TYPE_CHECKING:
10
12
 
11
13
  logger = logging.getLogger(__name__)
12
14
 
13
- class BaseAgentUserInputMessageProcessor(ABC):
15
+ class BaseAgentUserInputMessageProcessor(ABC, metaclass=AgentUserInputMessageProcessorMeta):
14
16
  """
15
17
  Abstract base class for agent user input message processors.
16
18
  These processors can modify an AgentInputUserMessage, specifically from a user,
@@ -18,12 +20,13 @@ class BaseAgentUserInputMessageProcessor(ABC):
18
20
  Subclasses should be instantiated and passed to the AgentSpecification.
19
21
  """
20
22
 
21
- def get_name(self) -> str:
23
+ @classmethod
24
+ def get_name(cls) -> str:
22
25
  """
23
26
  Returns the unique registration name for this processor.
24
27
  Defaults to the class name. Can be overridden by subclasses.
25
28
  """
26
- return self.__class__.__name__
29
+ return cls.__name__
27
30
 
28
31
  @abstractmethod
29
32
  async def process(self,
@@ -16,7 +16,8 @@ class PassthroughInputProcessor(BaseAgentUserInputMessageProcessor):
16
16
  A processor that returns the message unchanged.
17
17
  Can be used as a default or for testing.
18
18
  """
19
- def get_name(self) -> str:
19
+ @classmethod
20
+ def get_name(cls) -> str:
20
21
  return "PassthroughInputProcessor"
21
22
 
22
23
  async def process(self,
@@ -37,7 +37,7 @@ class AgentUserInputMessageProcessorMeta(ABCMeta):
37
37
  # Create definition using name and the class itself
38
38
  definition = AgentUserInputMessageProcessorDefinition(name=processor_name, processor_class=cls)
39
39
  default_input_processor_registry.register_processor(definition)
40
- logger.info(f"Auto-registered input processor: '{processor_name}' from class {name}")
40
+ logger.info(f"Auto-registered input processor: '{processor_name}' from class {name} (no schema).")
41
41
 
42
42
  except AttributeError as e:
43
43
  # Catch if get_name is missing
@@ -60,6 +60,25 @@ class AgentUserInputMessageProcessorRegistry(metaclass=SingletonMeta):
60
60
  logger.debug(f"Input processor definition with name '{name}' not found in registry.")
61
61
  return definition
62
62
 
63
+ def get_processor(self, name: str) -> Optional['BaseAgentUserInputMessageProcessor']:
64
+ """
65
+ Retrieves an instance of an input processor by its name.
66
+
67
+ Args:
68
+ name: The name of the input processor to retrieve.
69
+
70
+ Returns:
71
+ An instance of the BaseAgentUserInputMessageProcessor if found and instantiable, otherwise None.
72
+ """
73
+ definition = self.get_processor_definition(name)
74
+ if definition:
75
+ try:
76
+ return definition.processor_class()
77
+ except Exception as e:
78
+ logger.error(f"Failed to instantiate input processor '{name}' from class '{definition.processor_class.__name__}': {e}", exc_info=True)
79
+ return None
80
+ return None
81
+
63
82
  def list_processor_names(self) -> List[str]:
64
83
  """
65
84
  Returns a list of names of all registered input processor definitions.
@@ -3,6 +3,8 @@ import logging
3
3
  from abc import ABC, abstractmethod
4
4
  from typing import TYPE_CHECKING
5
5
 
6
+ from .processor_meta import LLMResponseProcessorMeta
7
+
6
8
  if TYPE_CHECKING:
7
9
  from autobyteus.agent.context import AgentContext # MODIFIED IMPORT
8
10
  from autobyteus.agent.events import LLMCompleteResponseReceivedEvent
@@ -10,7 +12,7 @@ if TYPE_CHECKING:
10
12
 
11
13
  logger = logging.getLogger(__name__)
12
14
 
13
- class BaseLLMResponseProcessor(ABC):
15
+ class BaseLLMResponseProcessor(ABC, metaclass=LLMResponseProcessorMeta):
14
16
  """
15
17
  Abstract base class for LLM response processors.
16
18
  These processors analyze the LLM's textual response. If they identify a specific
@@ -19,13 +21,14 @@ class BaseLLMResponseProcessor(ABC):
19
21
  Subclasses should be instantiated and passed to the AgentSpecification.
20
22
  """
21
23
 
22
- def get_name(self) -> str:
24
+ @classmethod
25
+ def get_name(cls) -> str:
23
26
  """
24
27
  Returns the unique registration name for this processor.
25
28
  Defaults to the class name. Should be overridden by subclasses
26
29
  to provide a stable, user-friendly name (e.g., "xml_tool_usage").
27
30
  """
28
- return self.__class__.__name__
31
+ return cls.__name__
29
32
 
30
33
  @abstractmethod
31
34
  async def process_response(self, response: 'CompleteResponse', context: 'AgentContext', triggering_event: 'LLMCompleteResponseReceivedEvent') -> bool:
@@ -29,7 +29,7 @@ class LLMResponseProcessorMeta(ABCMeta):
29
29
 
30
30
  definition = LLMResponseProcessorDefinition(name=processor_name, processor_class=cls)
31
31
  default_llm_response_processor_registry.register_processor(definition)
32
- logger.info(f"Auto-registered LLM response processor: '{processor_name}' from class {name}")
32
+ logger.info(f"Auto-registered LLM response processor: '{processor_name}' from class {name} (no schema).")
33
33
 
34
34
  except AttributeError as e:
35
35
  logger.error(f"LLM response processor class {name} is missing required static/class method 'get_name' ({e}). Skipping registration.")