autobyteus 1.1.2__py3-none-any.whl → 1.1.4__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 (171) hide show
  1. autobyteus/agent/agent.py +1 -1
  2. autobyteus/agent/bootstrap_steps/__init__.py +2 -0
  3. autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +2 -0
  4. autobyteus/agent/bootstrap_steps/mcp_server_prewarming_step.py +71 -0
  5. autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +4 -2
  6. autobyteus/agent/context/agent_config.py +36 -5
  7. autobyteus/agent/events/worker_event_dispatcher.py +1 -2
  8. autobyteus/agent/handlers/inter_agent_message_event_handler.py +1 -1
  9. autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +2 -2
  10. autobyteus/agent/handlers/tool_result_event_handler.py +48 -20
  11. autobyteus/agent/handlers/user_input_message_event_handler.py +1 -1
  12. autobyteus/agent/input_processor/__init__.py +1 -7
  13. autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +41 -12
  14. autobyteus/agent/message/context_file_type.py +6 -0
  15. autobyteus/agent/message/send_message_to.py +68 -99
  16. autobyteus/agent/phases/discover.py +2 -1
  17. autobyteus/agent/runtime/agent_worker.py +25 -34
  18. autobyteus/agent/shutdown_steps/__init__.py +17 -0
  19. autobyteus/agent/shutdown_steps/agent_shutdown_orchestrator.py +63 -0
  20. autobyteus/agent/shutdown_steps/base_shutdown_step.py +33 -0
  21. autobyteus/agent/shutdown_steps/llm_instance_cleanup_step.py +45 -0
  22. autobyteus/agent/shutdown_steps/mcp_server_cleanup_step.py +32 -0
  23. autobyteus/agent/tool_execution_result_processor/__init__.py +9 -0
  24. autobyteus/agent/tool_execution_result_processor/base_processor.py +46 -0
  25. autobyteus/agent/tool_execution_result_processor/processor_definition.py +36 -0
  26. autobyteus/agent/tool_execution_result_processor/processor_meta.py +36 -0
  27. autobyteus/agent/tool_execution_result_processor/processor_registry.py +70 -0
  28. autobyteus/agent/workspace/base_workspace.py +17 -2
  29. autobyteus/cli/__init__.py +1 -1
  30. autobyteus/cli/cli_display.py +1 -1
  31. autobyteus/cli/workflow_tui/__init__.py +4 -0
  32. autobyteus/cli/workflow_tui/app.py +210 -0
  33. autobyteus/cli/workflow_tui/state.py +189 -0
  34. autobyteus/cli/workflow_tui/widgets/__init__.py +6 -0
  35. autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +149 -0
  36. autobyteus/cli/workflow_tui/widgets/focus_pane.py +335 -0
  37. autobyteus/cli/workflow_tui/widgets/logo.py +27 -0
  38. autobyteus/cli/workflow_tui/widgets/renderables.py +70 -0
  39. autobyteus/cli/workflow_tui/widgets/shared.py +51 -0
  40. autobyteus/cli/workflow_tui/widgets/status_bar.py +14 -0
  41. autobyteus/events/event_types.py +3 -0
  42. autobyteus/llm/api/lmstudio_llm.py +37 -0
  43. autobyteus/llm/api/openai_compatible_llm.py +20 -3
  44. autobyteus/llm/llm_factory.py +2 -0
  45. autobyteus/llm/lmstudio_provider.py +89 -0
  46. autobyteus/llm/providers.py +1 -0
  47. autobyteus/llm/token_counter/token_counter_factory.py +2 -0
  48. autobyteus/tools/__init__.py +2 -0
  49. autobyteus/tools/ask_user_input.py +2 -1
  50. autobyteus/tools/base_tool.py +2 -0
  51. autobyteus/tools/bash/bash_executor.py +2 -1
  52. autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +2 -0
  53. autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +3 -0
  54. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -0
  55. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -0
  56. autobyteus/tools/browser/standalone/google_search_ui.py +2 -0
  57. autobyteus/tools/browser/standalone/navigate_to.py +2 -0
  58. autobyteus/tools/browser/standalone/web_page_pdf_generator.py +3 -0
  59. autobyteus/tools/browser/standalone/webpage_image_downloader.py +3 -0
  60. autobyteus/tools/browser/standalone/webpage_reader.py +2 -0
  61. autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +3 -0
  62. autobyteus/tools/file/file_reader.py +36 -9
  63. autobyteus/tools/file/file_writer.py +37 -9
  64. autobyteus/tools/functional_tool.py +5 -4
  65. autobyteus/tools/image_downloader.py +2 -0
  66. autobyteus/tools/mcp/__init__.py +10 -7
  67. autobyteus/tools/mcp/call_handlers/__init__.py +0 -2
  68. autobyteus/tools/mcp/config_service.py +1 -6
  69. autobyteus/tools/mcp/factory.py +12 -26
  70. autobyteus/tools/mcp/server/__init__.py +16 -0
  71. autobyteus/tools/mcp/server/base_managed_mcp_server.py +139 -0
  72. autobyteus/tools/mcp/server/http_managed_mcp_server.py +29 -0
  73. autobyteus/tools/mcp/server/proxy.py +36 -0
  74. autobyteus/tools/mcp/server/stdio_managed_mcp_server.py +33 -0
  75. autobyteus/tools/mcp/server_instance_manager.py +93 -0
  76. autobyteus/tools/mcp/tool.py +28 -46
  77. autobyteus/tools/mcp/tool_registrar.py +179 -0
  78. autobyteus/tools/mcp/types.py +10 -21
  79. autobyteus/tools/pdf_downloader.py +2 -1
  80. autobyteus/tools/registry/tool_definition.py +20 -7
  81. autobyteus/tools/registry/tool_registry.py +75 -28
  82. autobyteus/tools/timer.py +2 -0
  83. autobyteus/tools/tool_category.py +14 -4
  84. autobyteus/tools/tool_meta.py +6 -1
  85. autobyteus/tools/tool_origin.py +10 -0
  86. autobyteus/workflow/agentic_workflow.py +93 -0
  87. autobyteus/{agent/workflow → workflow}/base_agentic_workflow.py +19 -27
  88. autobyteus/workflow/bootstrap_steps/__init__.py +20 -0
  89. autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +34 -0
  90. autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +23 -0
  91. autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +41 -0
  92. autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +108 -0
  93. autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +50 -0
  94. autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +25 -0
  95. autobyteus/workflow/context/__init__.py +17 -0
  96. autobyteus/workflow/context/team_manager.py +147 -0
  97. autobyteus/workflow/context/workflow_config.py +30 -0
  98. autobyteus/workflow/context/workflow_context.py +61 -0
  99. autobyteus/workflow/context/workflow_node_config.py +76 -0
  100. autobyteus/workflow/context/workflow_runtime_state.py +53 -0
  101. autobyteus/workflow/events/__init__.py +29 -0
  102. autobyteus/workflow/events/workflow_event_dispatcher.py +39 -0
  103. autobyteus/workflow/events/workflow_events.py +53 -0
  104. autobyteus/workflow/events/workflow_input_event_queue_manager.py +21 -0
  105. autobyteus/workflow/exceptions.py +8 -0
  106. autobyteus/workflow/factory/__init__.py +9 -0
  107. autobyteus/workflow/factory/workflow_factory.py +99 -0
  108. autobyteus/workflow/handlers/__init__.py +19 -0
  109. autobyteus/workflow/handlers/base_workflow_event_handler.py +16 -0
  110. autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py +61 -0
  111. autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +27 -0
  112. autobyteus/workflow/handlers/process_user_message_event_handler.py +46 -0
  113. autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +39 -0
  114. autobyteus/workflow/handlers/workflow_event_handler_registry.py +23 -0
  115. autobyteus/workflow/phases/__init__.py +11 -0
  116. autobyteus/workflow/phases/workflow_operational_phase.py +19 -0
  117. autobyteus/workflow/phases/workflow_phase_manager.py +48 -0
  118. autobyteus/workflow/runtime/__init__.py +13 -0
  119. autobyteus/workflow/runtime/workflow_runtime.py +82 -0
  120. autobyteus/workflow/runtime/workflow_worker.py +117 -0
  121. autobyteus/workflow/shutdown_steps/__init__.py +17 -0
  122. autobyteus/workflow/shutdown_steps/agent_team_shutdown_step.py +42 -0
  123. autobyteus/workflow/shutdown_steps/base_workflow_shutdown_step.py +16 -0
  124. autobyteus/workflow/shutdown_steps/bridge_cleanup_step.py +28 -0
  125. autobyteus/workflow/shutdown_steps/sub_workflow_shutdown_step.py +41 -0
  126. autobyteus/workflow/shutdown_steps/workflow_shutdown_orchestrator.py +35 -0
  127. autobyteus/workflow/streaming/__init__.py +26 -0
  128. autobyteus/workflow/streaming/agent_event_bridge.py +48 -0
  129. autobyteus/workflow/streaming/agent_event_multiplexer.py +70 -0
  130. autobyteus/workflow/streaming/workflow_event_bridge.py +50 -0
  131. autobyteus/workflow/streaming/workflow_event_notifier.py +83 -0
  132. autobyteus/workflow/streaming/workflow_event_stream.py +33 -0
  133. autobyteus/workflow/streaming/workflow_stream_event_payloads.py +28 -0
  134. autobyteus/workflow/streaming/workflow_stream_events.py +45 -0
  135. autobyteus/workflow/utils/__init__.py +9 -0
  136. autobyteus/workflow/utils/wait_for_idle.py +46 -0
  137. autobyteus/workflow/workflow_builder.py +151 -0
  138. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/METADATA +16 -13
  139. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/RECORD +156 -75
  140. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/top_level.txt +1 -0
  141. examples/__init__.py +1 -0
  142. examples/discover_phase_transitions.py +104 -0
  143. examples/run_browser_agent.py +260 -0
  144. examples/run_google_slides_agent.py +286 -0
  145. examples/run_mcp_browser_client.py +174 -0
  146. examples/run_mcp_google_slides_client.py +270 -0
  147. examples/run_mcp_list_tools.py +189 -0
  148. examples/run_poem_writer.py +274 -0
  149. examples/run_sqlite_agent.py +293 -0
  150. examples/workflow/__init__.py +1 -0
  151. examples/workflow/run_basic_research_workflow.py +189 -0
  152. examples/workflow/run_code_review_workflow.py +269 -0
  153. examples/workflow/run_debate_workflow.py +212 -0
  154. examples/workflow/run_workflow_with_tui.py +153 -0
  155. autobyteus/agent/context/agent_phase_manager.py +0 -264
  156. autobyteus/agent/context/phases.py +0 -49
  157. autobyteus/agent/group/__init__.py +0 -0
  158. autobyteus/agent/group/agent_group.py +0 -164
  159. autobyteus/agent/group/agent_group_context.py +0 -81
  160. autobyteus/agent/input_processor/content_prefixing_input_processor.py +0 -41
  161. autobyteus/agent/input_processor/metadata_appending_input_processor.py +0 -34
  162. autobyteus/agent/input_processor/passthrough_input_processor.py +0 -33
  163. autobyteus/agent/workflow/__init__.py +0 -11
  164. autobyteus/agent/workflow/agentic_workflow.py +0 -89
  165. autobyteus/tools/mcp/call_handlers/sse_handler.py +0 -22
  166. autobyteus/tools/mcp/registrar.py +0 -323
  167. autobyteus/workflow/simple_task.py +0 -98
  168. autobyteus/workflow/task.py +0 -147
  169. autobyteus/workflow/workflow.py +0 -49
  170. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/WHEEL +0 -0
  171. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,81 +0,0 @@
1
- # file: autobyteus/autobyteus/agent/group/agent_group_context.py
2
- import logging
3
- from typing import List, Dict, Optional, TYPE_CHECKING
4
-
5
- if TYPE_CHECKING:
6
- from autobyteus.agent.agent import Agent
7
-
8
- logger = logging.getLogger(__name__)
9
-
10
- class AgentGroupContext:
11
- """
12
- Stores contextual information about an agent group, including its ID,
13
- member agents, and the designated coordinator. Provides methods to
14
- discover agents within the group.
15
- """
16
- def __init__(self,
17
- group_id: str,
18
- agents: List['Agent'],
19
- coordinator_agent_id: str):
20
- """
21
- Initializes the AgentGroupContext.
22
- """
23
- if not group_id or not isinstance(group_id, str):
24
- raise ValueError("AgentGroupContext requires a non-empty string 'group_id'.")
25
- if not coordinator_agent_id or not isinstance(coordinator_agent_id, str):
26
- raise ValueError("AgentGroupContext requires a non-empty string 'coordinator_agent_id'.")
27
- if not agents:
28
- raise ValueError("AgentGroupContext requires a non-empty list of 'agents'.")
29
-
30
- from autobyteus.agent.agent import Agent as AgentClassRef
31
- if not all(isinstance(agent, AgentClassRef) for agent in agents):
32
- raise TypeError("All items in 'agents' list must be instances of the 'Agent' class.")
33
-
34
- self.group_id: str = group_id
35
- self._agents_by_id: Dict[str, 'Agent'] = {agent.agent_id: agent for agent in agents}
36
- self._coordinator_agent_id: str = coordinator_agent_id
37
-
38
- if self._coordinator_agent_id not in self._agents_by_id:
39
- logger.error(f"Coordinator agent with ID '{self._coordinator_agent_id}' not found in the provided list of agents for group '{self.group_id}'.")
40
-
41
- logger.info(f"AgentGroupContext initialized for group_id '{self.group_id}'.")
42
-
43
- def get_agent(self, agent_id: str) -> Optional['Agent']:
44
- """
45
- Retrieves an agent from the group by its unique agent_id.
46
- """
47
- return self._agents_by_id.get(agent_id)
48
-
49
- def get_agents_by_role(self, role_name: str) -> List['Agent']:
50
- """
51
- Retrieves all agents within the group that match the specified role name.
52
- """
53
- if not isinstance(role_name, str):
54
- logger.warning(f"Attempted to get_agents_by_role with non-string role_name: {role_name} in group '{self.group_id}'.")
55
- return []
56
-
57
- matching_agents: List['Agent'] = [
58
- agent for agent in self._agents_by_id.values()
59
- if agent.context and agent.context.config and agent.context.config.role == role_name
60
- ]
61
-
62
- if not matching_agents:
63
- logger.debug(f"No agents found with role '{role_name}' in group '{self.group_id}'.")
64
- return matching_agents
65
-
66
- def get_coordinator_agent(self) -> Optional['Agent']:
67
- """
68
- Retrieves the designated coordinator agent for this group.
69
- """
70
- return self.get_agent(self._coordinator_agent_id)
71
-
72
- def get_all_agents(self) -> List['Agent']:
73
- """
74
- Retrieves all agents currently part of this group.
75
- """
76
- return list(self._agents_by_id.values())
77
-
78
- def __repr__(self) -> str:
79
- return (f"<AgentGroupContext group_id='{self.group_id}', "
80
- f"num_agents={len(self._agents_by_id)}, "
81
- f"coordinator_id='{self._coordinator_agent_id}'>")
@@ -1,41 +0,0 @@
1
- # file: autobyteus/autobyteus/agent/input_processor/content_prefixing_input_processor.py
2
- import logging
3
- from typing import TYPE_CHECKING
4
-
5
- from .base_user_input_processor import BaseAgentUserInputMessageProcessor
6
-
7
- if TYPE_CHECKING:
8
- from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
9
- from autobyteus.agent.context import AgentContext # Composite AgentContext
10
- from autobyteus.agent.events import UserMessageReceivedEvent
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
- class ContentPrefixingInputProcessor(BaseAgentUserInputMessageProcessor):
15
- """
16
- A processor that adds a predefined prefix to the message content.
17
- The prefix is defined by the agent's custom_data (in AgentRuntimeState) or a default.
18
- Example prefix key in custom_data: "content_prefix"
19
- """
20
- DEFAULT_PREFIX = "[Processed Message] "
21
-
22
- async def process(self,
23
- message: 'AgentInputUserMessage',
24
- context: 'AgentContext',
25
- triggering_event: 'UserMessageReceivedEvent') -> 'AgentInputUserMessage':
26
- """
27
- Handles the message by prefixing its content.
28
- The 'triggering_event' parameter is ignored by this processor.
29
- """
30
- agent_id = context.agent_id # Convenience property
31
- logger.debug(f"Agent '{agent_id}': ContentPrefixingInputProcessor processing message.")
32
-
33
- # Access custom_data via convenience property (or context.state.custom_data)
34
- prefix = context.custom_data.get("content_prefix", self.DEFAULT_PREFIX)
35
- if not isinstance(prefix, str):
36
- logger.warning(f"Agent '{agent_id}': 'content_prefix' in custom_data is not a string. Using default prefix. Found: {type(prefix)}")
37
- prefix = self.DEFAULT_PREFIX
38
-
39
- message.content = prefix + message.content
40
- logger.info(f"Agent '{agent_id}': Prefixed message content with '{prefix}'.")
41
- return message
@@ -1,34 +0,0 @@
1
- # file: autobyteus/autobyteus/agent/input_processor/metadata_appending_input_processor.py
2
- import logging
3
- from typing import TYPE_CHECKING
4
-
5
- from .base_user_input_processor import BaseAgentUserInputMessageProcessor
6
-
7
- if TYPE_CHECKING:
8
- from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
9
- from autobyteus.agent.context import AgentContext # Composite AgentContext
10
- from autobyteus.agent.events import UserMessageReceivedEvent
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
- class MetadataAppendingInputProcessor(BaseAgentUserInputMessageProcessor):
15
- """
16
- A processor that appends fixed metadata to the message.
17
- Example: Appends agent_id and config_name to metadata.
18
- """
19
- async def process(self,
20
- message: 'AgentInputUserMessage',
21
- context: 'AgentContext',
22
- triggering_event: 'UserMessageReceivedEvent') -> 'AgentInputUserMessage':
23
- """
24
- Handles the message by appending metadata.
25
- The 'triggering_event' parameter is ignored by this processor.
26
- """
27
- agent_id = context.agent_id
28
- config_name = context.config.name
29
-
30
- logger.debug(f"Agent '{agent_id}': MetadataAppendingInputProcessor processing message.")
31
- message.metadata["processed_by_agent_id"] = agent_id
32
- message.metadata["processed_with_config_name"] = config_name
33
- logger.info(f"Agent '{agent_id}': Appended 'processed_by_agent_id' and 'processed_with_config_name' to message metadata.")
34
- return message
@@ -1,33 +0,0 @@
1
- # file: autobyteus/autobyteus/agent/input_processor/passthrough_input_processor.py
2
- import logging
3
- from typing import TYPE_CHECKING
4
-
5
- from .base_user_input_processor import BaseAgentUserInputMessageProcessor
6
-
7
- if TYPE_CHECKING:
8
- from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
9
- from autobyteus.agent.context import AgentContext # Composite AgentContext
10
- from autobyteus.agent.events import UserMessageReceivedEvent
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
- class PassthroughInputProcessor(BaseAgentUserInputMessageProcessor):
15
- """
16
- A processor that returns the message unchanged.
17
- Can be used as a default or for testing.
18
- """
19
- @classmethod
20
- def get_name(cls) -> str:
21
- return "PassthroughInputProcessor"
22
-
23
- async def process(self,
24
- message: 'AgentInputUserMessage',
25
- context: 'AgentContext',
26
- triggering_event: 'UserMessageReceivedEvent') -> 'AgentInputUserMessage':
27
- """
28
- Handles the message by returning it without modification.
29
- The 'triggering_event' parameter is ignored by this processor.
30
- """
31
- agent_id = context.agent_id # Convenience property
32
- logger.debug(f"Agent '{agent_id}': PassthroughInputProcessor received message, returning as is.")
33
- return message
@@ -1,11 +0,0 @@
1
- # file: autobyteus/autobyteus/agent/workflow/__init__.py
2
- """
3
- Components for defining and running agentic workflows.
4
- """
5
- from .agentic_workflow import AgenticWorkflow
6
- from .base_agentic_workflow import BaseAgenticWorkflow
7
-
8
- __all__ = [
9
- "AgenticWorkflow",
10
- "BaseAgenticWorkflow",
11
- ]
@@ -1,89 +0,0 @@
1
- # file: autobyteus/autobyteus/agent/workflow/agentic_workflow.py
2
- import logging
3
- import uuid
4
- from typing import List, Dict, Optional, Any, cast
5
-
6
- from autobyteus.agent.context.agent_config import AgentConfig
7
- from autobyteus.agent.group.agent_group import AgentGroup
8
-
9
- logger = logging.getLogger(__name__)
10
-
11
- class AgenticWorkflow:
12
- """
13
- A concrete class for defining and running multi-agent workflows declaratively.
14
- It internally manages an AgentGroup and provides a user-friendly interface
15
- to process tasks.
16
- """
17
- def __init__(self,
18
- agent_configs: List[AgentConfig],
19
- coordinator_config_name: str,
20
- workflow_id: Optional[str] = None,
21
- input_param_name: str = "input",
22
- ):
23
- """
24
- Initializes the AgenticWorkflow.
25
-
26
- Args:
27
- agent_configs: List of pre-made AgentConfig instances for the agents in this workflow.
28
- coordinator_config_name: Name of the agent config to be used as coordinator.
29
- workflow_id: Optional. A unique ID for this workflow instance. Auto-generated if None.
30
- input_param_name: The key to use in `process(**kwargs)` to find the initial
31
- input string for the coordinator. Defaults to "input".
32
- """
33
- self.workflow_id: str = workflow_id or f"workflow_{uuid.uuid4()}"
34
- self._input_param_name: str = input_param_name
35
-
36
- logger.info(f"Initializing AgenticWorkflow '{self.workflow_id}'. "
37
- f"Input parameter name for process(): '{self._input_param_name}'.")
38
-
39
- # The AgentGroup is now initialized directly with the user-provided configs.
40
- self.agent_group: AgentGroup = AgentGroup(
41
- agent_configs=agent_configs,
42
- coordinator_config_name=coordinator_config_name,
43
- group_id=f"group_for_{self.workflow_id}",
44
- )
45
- logger.info(f"AgenticWorkflow '{self.workflow_id}' successfully instantiated internal AgentGroup '{self.agent_group.group_id}'.")
46
-
47
- async def process(self, **kwargs: Any) -> Any:
48
- logger.info(f"AgenticWorkflow '{self.workflow_id}' received process request with kwargs: {list(kwargs.keys())}")
49
-
50
- initial_input_content = kwargs.get(self._input_param_name)
51
- if initial_input_content is None:
52
- raise ValueError(f"Required input parameter '{self._input_param_name}' not found in process() arguments.")
53
- if not isinstance(initial_input_content, str):
54
- raise ValueError(f"Input parameter '{self._input_param_name}' must be a string, "
55
- f"got {type(initial_input_content).__name__}.")
56
-
57
- user_id: Optional[str] = cast(Optional[str], kwargs.get("user_id")) if isinstance(kwargs.get("user_id"), str) else None
58
-
59
- logger.debug(f"AgenticWorkflow '{self.workflow_id}': Extracted initial input for coordinator: '{initial_input_content[:100]}...'")
60
-
61
- result = await self.agent_group.process_task_for_coordinator(
62
- initial_input_content=initial_input_content,
63
- user_id=user_id
64
- )
65
-
66
- return result
67
-
68
-
69
- async def start(self) -> None:
70
- logger.info(f"AgenticWorkflow '{self.workflow_id}' received start() request. Delegating to AgentGroup.")
71
- await self.agent_group.start()
72
-
73
- async def stop(self, timeout: float = 10.0) -> None:
74
- logger.info(f"AgenticWorkflow '{self.workflow_id}' received stop() request. Delegating to AgentGroup.")
75
- await self.agent_group.stop(timeout)
76
-
77
- @property
78
- def is_running(self) -> bool:
79
- return self.agent_group.is_running
80
-
81
- @property
82
- def group_id(self) -> str:
83
- return self.agent_group.group_id
84
-
85
- def __repr__(self) -> str:
86
- return (f"<AgenticWorkflow workflow_id='{self.workflow_id}', "
87
- f"group_id='{self.agent_group.group_id}', "
88
- f"coordinator='{self.agent_group.coordinator_config_name}', "
89
- f"is_running={self.is_running}>")
@@ -1,22 +0,0 @@
1
- # file: autobyteus/autobyteus/tools/mcp/call_handlers/sse_handler.py
2
- import logging
3
- from typing import Dict, Any, TYPE_CHECKING
4
-
5
- from .base_handler import McpCallHandler
6
-
7
- if TYPE_CHECKING:
8
- from ..types import BaseMcpConfig
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
- class SseMcpCallHandler(McpCallHandler):
13
- """Placeholder handler for MCP tool calls over SSE."""
14
-
15
- async def handle_call(
16
- self,
17
- config: 'BaseMcpConfig',
18
- remote_tool_name: str,
19
- arguments: Dict[str, Any]
20
- ) -> Any:
21
- logger.warning(f"SseMcpCallHandler for server '{config.server_id}' is a placeholder and not fully implemented.")
22
- raise NotImplementedError(f"SSE transport is not fully implemented for tool call to '{remote_tool_name}'.")
@@ -1,323 +0,0 @@
1
- # file: autobyteus/autobyteus/tools/mcp/registrar.py
2
- import logging
3
- from typing import Any, Dict, List, Optional, Union
4
-
5
- # Import the new handler architecture components
6
- from .call_handlers import (
7
- McpCallHandler,
8
- StdioMcpCallHandler,
9
- StreamableHttpMcpCallHandler,
10
- SseMcpCallHandler
11
- )
12
-
13
- # Consolidated imports from the autobyteus.autobyteus.mcp package public API
14
- from autobyteus.tools.mcp import (
15
- McpConfigService,
16
- McpSchemaMapper,
17
- McpToolFactory,
18
- McpTransportType,
19
- BaseMcpConfig
20
- )
21
-
22
- from autobyteus.tools.registry import ToolRegistry, ToolDefinition
23
- from autobyteus.tools.tool_category import ToolCategory
24
- from autobyteus.utils.singleton import SingletonMeta
25
- from mcp import types as mcp_types
26
-
27
-
28
- logger = logging.getLogger(__name__)
29
-
30
- class McpToolRegistrar(metaclass=SingletonMeta):
31
- """
32
- Orchestrates the discovery of remote MCP tools and their registration
33
- with the AutoByteUs ToolRegistry using a handler-based architecture.
34
- This class is a singleton.
35
- """
36
- def __init__(self):
37
- """
38
- Initializes the McpToolRegistrar singleton.
39
- It retrieves singleton instances of its service dependencies and initializes
40
- its internal state for tracking registered tools.
41
- """
42
- self._config_service: McpConfigService = McpConfigService()
43
- self._tool_registry: ToolRegistry = ToolRegistry()
44
-
45
- # The handler registry maps a transport type to a reusable handler instance.
46
- self._handler_registry: Dict[McpTransportType, McpCallHandler] = {
47
- McpTransportType.STDIO: StdioMcpCallHandler(),
48
- McpTransportType.STREAMABLE_HTTP: StreamableHttpMcpCallHandler(),
49
- McpTransportType.SSE: SseMcpCallHandler(),
50
- }
51
-
52
- # Internal state to track which ToolDefinitions were registered from which server.
53
- self._registered_tools_by_server: Dict[str, List[ToolDefinition]] = {}
54
-
55
- logger.info(f"McpToolRegistrar initialized with {len(self._handler_registry)} call handlers.")
56
-
57
- def _create_tool_definition_from_remote(
58
- self,
59
- remote_tool: mcp_types.Tool,
60
- server_config: BaseMcpConfig,
61
- handler: McpCallHandler,
62
- schema_mapper: McpSchemaMapper
63
- ) -> ToolDefinition:
64
- """
65
- Maps a single remote tool from an MCP server to an AutoByteUs ToolDefinition.
66
- This is a helper method to centralize the mapping logic.
67
- """
68
- if hasattr(remote_tool, 'model_dump_json'):
69
- logger.debug(f"Processing remote tool from server '{server_config.server_id}':\n{remote_tool.model_dump_json(indent=2)}")
70
-
71
- actual_arg_schema = schema_mapper.map_to_autobyteus_schema(remote_tool.inputSchema)
72
- actual_desc = remote_tool.description
73
-
74
- registered_name = remote_tool.name
75
- if server_config.tool_name_prefix:
76
- registered_name = f"{server_config.tool_name_prefix.rstrip('_')}_{remote_tool.name}"
77
-
78
- tool_factory = McpToolFactory(
79
- mcp_server_config=server_config,
80
- mcp_remote_tool_name=remote_tool.name,
81
- mcp_call_handler=handler,
82
- registered_tool_name=registered_name,
83
- tool_description=actual_desc,
84
- tool_argument_schema=actual_arg_schema
85
- )
86
-
87
- tool_def = ToolDefinition(
88
- name=registered_name,
89
- description=actual_desc,
90
- argument_schema=actual_arg_schema,
91
- category=ToolCategory.MCP,
92
- custom_factory=tool_factory.create_tool,
93
- config_schema=None,
94
- tool_class=None
95
- )
96
- return tool_def
97
-
98
- async def discover_and_register_tools(self, mcp_config: Optional[Union[BaseMcpConfig, Dict[str, Any]]] = None) -> List[ToolDefinition]:
99
- """
100
- Discovers tools from MCP servers and registers them.
101
-
102
- If `mcp_config` is provided (as a validated object or a raw dictionary),
103
- it discovers tools only from that specific server, unregistering any
104
- of its old tools first.
105
-
106
- If `mcp_config` is None, it unregisters all existing MCP tools and then
107
- discovers tools from all enabled servers found in the McpConfigService.
108
-
109
- Returns:
110
- A list of ToolDefinition objects for all tools that were successfully
111
- discovered and registered during this call.
112
- """
113
- configs_to_process: List[BaseMcpConfig]
114
-
115
- if mcp_config:
116
- validated_config: BaseMcpConfig
117
- # If the user provided a raw dictionary, parse and add it via the service.
118
- if isinstance(mcp_config, dict):
119
- try:
120
- validated_config = self._config_service.load_config(mcp_config)
121
- except ValueError as e:
122
- logger.error(f"Failed to parse provided MCP config dictionary: {e}")
123
- raise
124
- # If a validated object was passed, add it to the service to ensure it's known.
125
- elif isinstance(mcp_config, BaseMcpConfig):
126
- validated_config = self._config_service.add_config(mcp_config)
127
- else:
128
- raise TypeError(f"mcp_config must be a BaseMcpConfig object or a dictionary, not {type(mcp_config)}.")
129
-
130
- logger.info(f"Starting targeted MCP tool discovery for server: {validated_config.server_id}")
131
- # Unregister any existing tools from this server before re-discovering.
132
- self.unregister_tools_from_server(validated_config.server_id)
133
- configs_to_process = [validated_config]
134
- else:
135
- logger.info("Starting full MCP tool discovery and registration process. Unregistering all existing MCP tools first.")
136
- # Get a copy of all server IDs that have tools registered.
137
- all_server_ids = list(self._registered_tools_by_server.keys())
138
- for server_id in all_server_ids:
139
- self.unregister_tools_from_server(server_id)
140
-
141
- # The registrar's internal state should now be empty, but a clear() is a good safeguard.
142
- self._registered_tools_by_server.clear()
143
-
144
- configs_to_process = self._config_service.get_all_configs()
145
-
146
- if not configs_to_process:
147
- logger.info("No MCP server configurations to process. Skipping discovery.")
148
- return []
149
-
150
- schema_mapper = McpSchemaMapper()
151
- registered_tool_definitions: List[ToolDefinition] = []
152
- for server_config in configs_to_process:
153
- if not server_config.enabled:
154
- logger.info(f"MCP server '{server_config.server_id}' is disabled. Skipping.")
155
- continue
156
-
157
- logger.info(f"Discovering tools from MCP server: '{server_config.server_id}' ({server_config.transport_type.value})")
158
- try:
159
- handler = self._handler_registry.get(server_config.transport_type)
160
- if not handler:
161
- logger.error(f"No MCP call handler found for transport type '{server_config.transport_type.value}' on server '{server_config.server_id}'.")
162
- continue
163
-
164
- remote_tools_result = await handler.handle_call(
165
- config=server_config,
166
- remote_tool_name="list_tools",
167
- arguments={}
168
- )
169
-
170
- if not isinstance(remote_tools_result, mcp_types.ListToolsResult):
171
- logger.error(f"Expected ListToolsResult from handler for 'list_tools', but got {type(remote_tools_result)}. Skipping server '{server_config.server_id}'.")
172
- continue
173
-
174
- actual_remote_tools: list[mcp_types.Tool] = remote_tools_result.tools
175
- logger.info(f"Discovered {len(actual_remote_tools)} tools from server '{server_config.server_id}'.")
176
-
177
- for remote_tool in actual_remote_tools:
178
- try:
179
- tool_def = self._create_tool_definition_from_remote(remote_tool, server_config, handler, schema_mapper)
180
-
181
- self._tool_registry.register_tool(tool_def)
182
- self._registered_tools_by_server.setdefault(server_config.server_id, []).append(tool_def)
183
- registered_tool_definitions.append(tool_def)
184
-
185
- logger.info(f"Successfully registered MCP tool '{remote_tool.name}' from server '{server_config.server_id}' as '{tool_def.name}'.")
186
-
187
- except Exception as e_tool:
188
- logger.error(f"Failed to process or register remote tool '{remote_tool.name}' from server '{server_config.server_id}': {e_tool}", exc_info=True)
189
-
190
- except Exception as e_server:
191
- logger.error(f"Failed to discover tools from MCP server '{server_config.server_id}': {e_server}", exc_info=True)
192
-
193
- logger.info(f"MCP tool discovery and registration process completed. Total tools registered: {len(registered_tool_definitions)}.")
194
- return registered_tool_definitions
195
-
196
- async def list_remote_tools(self, mcp_config: Union[BaseMcpConfig, Dict[str, Any]]) -> List[ToolDefinition]:
197
- """
198
- Previews tools from a remote MCP server without registering them or storing the configuration.
199
- This is a stateless "dry-run" or "preview" operation.
200
-
201
- Args:
202
- mcp_config: A single MCP server configuration, as a validated object or a raw dictionary.
203
-
204
- Returns:
205
- A list of ToolDefinition objects for all tools discovered on the server.
206
-
207
- Raises:
208
- TypeError: If mcp_config is not a supported type.
209
- ValueError: If the provided configuration is invalid.
210
- RuntimeError: If the connection or tool call to the remote server fails.
211
- """
212
- validated_config: BaseMcpConfig
213
- if isinstance(mcp_config, dict):
214
- # Use the static method to parse the config without storing it in the service.
215
- validated_config = McpConfigService.parse_mcp_config_dict(mcp_config)
216
- elif isinstance(mcp_config, BaseMcpConfig):
217
- validated_config = mcp_config
218
- else:
219
- raise TypeError(f"mcp_config must be a BaseMcpConfig object or a dictionary, not {type(mcp_config)}.")
220
-
221
- logger.info(f"Previewing tools from MCP server: '{validated_config.server_id}' ({validated_config.transport_type.value})")
222
-
223
- schema_mapper = McpSchemaMapper()
224
- tool_definitions: List[ToolDefinition] = []
225
-
226
- try:
227
- handler = self._handler_registry.get(validated_config.transport_type)
228
- if not handler:
229
- raise ValueError(f"No MCP call handler found for transport type '{validated_config.transport_type.value}'.")
230
-
231
- remote_tools_result = await handler.handle_call(
232
- config=validated_config,
233
- remote_tool_name="list_tools",
234
- arguments={}
235
- )
236
-
237
- if not isinstance(remote_tools_result, mcp_types.ListToolsResult):
238
- error_msg = f"Expected ListToolsResult from handler, but got {type(remote_tools_result)}. Cannot preview tools from server '{validated_config.server_id}'."
239
- logger.error(error_msg)
240
- raise RuntimeError(error_msg)
241
-
242
- actual_remote_tools: list[mcp_types.Tool] = remote_tools_result.tools
243
- logger.info(f"Discovered {len(actual_remote_tools)} tools from server '{validated_config.server_id}' for preview.")
244
-
245
- for remote_tool in actual_remote_tools:
246
- try:
247
- tool_def = self._create_tool_definition_from_remote(remote_tool, validated_config, handler, schema_mapper)
248
- tool_definitions.append(tool_def)
249
- except Exception as e_tool:
250
- logger.error(f"Failed to map remote tool '{remote_tool.name}' from server '{validated_config.server_id}' during preview: {e_tool}", exc_info=True)
251
- # For a preview, we continue to show other valid tools.
252
-
253
- except Exception as e_server:
254
- logger.error(f"Failed to discover tools for preview from MCP server '{validated_config.server_id}': {e_server}", exc_info=True)
255
- # Re-raise server-level exceptions to the caller as per AC5.
256
- raise
257
-
258
- logger.info(f"MCP tool preview completed. Found {len(tool_definitions)} tools.")
259
- return tool_definitions
260
-
261
- def get_registered_tools_for_server(self, server_id: str) -> List[ToolDefinition]:
262
- """
263
- Returns a list of ToolDefinition objects that were successfully registered
264
- from a specific MCP server during the most recent discovery process.
265
-
266
- Args:
267
- server_id: The unique ID of the MCP server to query.
268
-
269
- Returns:
270
- A list of ToolDefinition objects. Returns an empty list if the server ID
271
- is not found or registered no tools.
272
- """
273
- return self._registered_tools_by_server.get(server_id, [])
274
-
275
- def get_all_registered_mcp_tools(self) -> List[ToolDefinition]:
276
- """
277
- Returns a flat list of all ToolDefinitions registered from any MCP server.
278
- """
279
- all_tools = []
280
- for server_tools in self._registered_tools_by_server.values():
281
- all_tools.extend(server_tools)
282
- return all_tools
283
-
284
- def is_server_registered(self, server_id: str) -> bool:
285
- """
286
- Checks if any tools from a specific MCP server are currently registered.
287
-
288
- Args:
289
- server_id: The unique ID of the MCP server.
290
-
291
- Returns:
292
- True if the server has tools registered, False otherwise.
293
- """
294
- is_registered = server_id in self._registered_tools_by_server
295
- logger.debug(f"Checking if server '{server_id}' is registered: {is_registered}")
296
- return is_registered
297
-
298
- def unregister_tools_from_server(self, server_id: str) -> bool:
299
- """
300
- Unregisters all tools associated with a given MCP server ID from the
301
- main ToolRegistry and removes the server from internal tracking.
302
-
303
- Args:
304
- server_id: The unique ID of the MCP server whose tools should be unregistered.
305
-
306
- Returns:
307
- True if the server was found and its tools were processed for unregistration,
308
- False if the server was not found in the registrar's state.
309
- """
310
- if not self.is_server_registered(server_id):
311
- logger.info(f"No tools found for server ID '{server_id}'. Nothing to unregister.")
312
- return False
313
-
314
- tools_to_unregister = self._registered_tools_by_server.get(server_id, [])
315
- logger.info(f"Unregistering {len(tools_to_unregister)} tools from server ID: '{server_id}'...")
316
-
317
- for tool_def in tools_to_unregister:
318
- self._tool_registry.unregister_tool(tool_def.name)
319
-
320
- # Remove the server from the tracking dictionary
321
- del self._registered_tools_by_server[server_id]
322
- logger.info(f"Successfully unregistered all tools and removed server '{server_id}' from registrar tracking.")
323
- return True