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
@@ -0,0 +1,179 @@
1
+ # file: autobyteus/autobyteus/tools/mcp/tool_registrar.py
2
+ import logging
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ # Consolidated imports from the autobyteus.autobyteus.mcp package public API
6
+ from .config_service import McpConfigService
7
+ from .factory import McpToolFactory
8
+ from .schema_mapper import McpSchemaMapper
9
+ from .server_instance_manager import McpServerInstanceManager
10
+ from .types import BaseMcpConfig
11
+ from .server import BaseManagedMcpServer
12
+
13
+ from autobyteus.tools.registry import ToolRegistry, ToolDefinition
14
+ from autobyteus.tools.tool_origin import ToolOrigin
15
+ from autobyteus.tools.tool_category import ToolCategory
16
+ from autobyteus.utils.singleton import SingletonMeta
17
+ from mcp import types as mcp_types
18
+
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ class McpToolRegistrar(metaclass=SingletonMeta):
23
+ """
24
+ Orchestrates the discovery of remote MCP tools and their registration
25
+ with the AutoByteUs ToolRegistry.
26
+ """
27
+ def __init__(self):
28
+ """
29
+ Initializes the McpToolRegistrar singleton.
30
+ """
31
+ self._config_service: McpConfigService = McpConfigService()
32
+ self._tool_registry: ToolRegistry = ToolRegistry()
33
+ self._instance_manager: McpServerInstanceManager = McpServerInstanceManager()
34
+ self._registered_tools_by_server: Dict[str, List[ToolDefinition]] = {}
35
+ logger.info("McpToolRegistrar initialized.")
36
+
37
+ async def _fetch_tools_from_server(self, server_config: BaseMcpConfig) -> List[mcp_types.Tool]:
38
+ """
39
+ Uses the instance manager to get a temporary, managed session for discovery.
40
+ """
41
+ async with self._instance_manager.managed_discovery_session(server_config) as discovery_server:
42
+ # The context manager guarantees the server is connected and will be closed.
43
+ remote_tools = await discovery_server.list_remote_tools()
44
+ return remote_tools
45
+
46
+ def _create_tool_definition_from_remote(
47
+ self,
48
+ remote_tool: mcp_types.Tool,
49
+ server_config: BaseMcpConfig,
50
+ schema_mapper: McpSchemaMapper
51
+ ) -> ToolDefinition:
52
+ """
53
+ Maps a single remote tool from an MCP server to an AutoByteUs ToolDefinition.
54
+ """
55
+ actual_arg_schema = schema_mapper.map_to_autobyteus_schema(remote_tool.inputSchema)
56
+ actual_desc = remote_tool.description
57
+
58
+ registered_name = remote_tool.name
59
+ if server_config.tool_name_prefix:
60
+ registered_name = f"{server_config.tool_name_prefix.rstrip('_')}_{remote_tool.name}"
61
+
62
+ tool_factory = McpToolFactory(
63
+ server_id=server_config.server_id,
64
+ remote_tool_name=remote_tool.name,
65
+ registered_tool_name=registered_name,
66
+ tool_description=actual_desc,
67
+ tool_argument_schema=actual_arg_schema
68
+ )
69
+
70
+ return ToolDefinition(
71
+ name=registered_name,
72
+ description=actual_desc,
73
+ argument_schema=actual_arg_schema,
74
+ origin=ToolOrigin.MCP,
75
+ category=server_config.server_id, # Use server_id as the category
76
+ metadata={"mcp_server_id": server_config.server_id}, # Store origin in generic metadata
77
+ custom_factory=tool_factory.create_tool,
78
+ config_schema=None,
79
+ tool_class=None
80
+ )
81
+
82
+ async def discover_and_register_tools(self, mcp_config: Optional[Union[BaseMcpConfig, Dict[str, Any]]] = None) -> List[ToolDefinition]:
83
+ """
84
+ Discovers tools from MCP servers and registers them.
85
+ """
86
+ configs_to_process: List[BaseMcpConfig]
87
+
88
+ if mcp_config:
89
+ validated_config: BaseMcpConfig
90
+ if isinstance(mcp_config, dict):
91
+ try:
92
+ validated_config = self._config_service.load_config(mcp_config)
93
+ except ValueError as e:
94
+ logger.error(f"Failed to parse provided MCP config dictionary: {e}")
95
+ raise
96
+ elif isinstance(mcp_config, BaseMcpConfig):
97
+ validated_config = self._config_service.add_config(mcp_config)
98
+ else:
99
+ raise TypeError(f"mcp_config must be a BaseMcpConfig object or a dictionary, not {type(mcp_config)}.")
100
+
101
+ logger.info(f"Starting targeted MCP tool discovery for server: {validated_config.server_id}")
102
+ self.unregister_tools_from_server(validated_config.server_id)
103
+ configs_to_process = [validated_config]
104
+ else:
105
+ logger.info("Starting full MCP tool discovery. Unregistering all existing MCP tools first.")
106
+ all_server_ids = list(self._registered_tools_by_server.keys())
107
+ for server_id in all_server_ids:
108
+ self.unregister_tools_from_server(server_id)
109
+ self._registered_tools_by_server.clear()
110
+ configs_to_process = self._config_service.get_all_configs()
111
+
112
+ if not configs_to_process:
113
+ logger.info("No MCP server configurations to process. Skipping discovery.")
114
+ return []
115
+
116
+ schema_mapper = McpSchemaMapper()
117
+ registered_tool_definitions: List[ToolDefinition] = []
118
+ for server_config in configs_to_process:
119
+ if not server_config.enabled:
120
+ logger.info(f"MCP server '{server_config.server_id}' is disabled. Skipping.")
121
+ continue
122
+
123
+ logger.info(f"Discovering tools from MCP server: '{server_config.server_id}'")
124
+
125
+ try:
126
+ remote_tools = await self._fetch_tools_from_server(server_config)
127
+ logger.info(f"Discovered {len(remote_tools)} tools from server '{server_config.server_id}'.")
128
+
129
+ for remote_tool in remote_tools:
130
+ try:
131
+ tool_def = self._create_tool_definition_from_remote(remote_tool, server_config, schema_mapper)
132
+ self._tool_registry.register_tool(tool_def)
133
+ self._registered_tools_by_server.setdefault(server_config.server_id, []).append(tool_def)
134
+ registered_tool_definitions.append(tool_def)
135
+ except Exception as e_tool:
136
+ logger.error(f"Failed to process or register remote tool '{remote_tool.name}': {e_tool}", exc_info=True)
137
+
138
+ except Exception as e_server:
139
+ logger.error(f"Failed to discover tools from MCP server '{server_config.server_id}': {e_server}", exc_info=True)
140
+
141
+ logger.info(f"MCP tool discovery and registration process completed. Total tools registered: {len(registered_tool_definitions)}.")
142
+ return registered_tool_definitions
143
+
144
+ async def list_remote_tools(self, mcp_config: Union[BaseMcpConfig, Dict[str, Any]]) -> List[ToolDefinition]:
145
+ validated_config: BaseMcpConfig
146
+ if isinstance(mcp_config, dict):
147
+ validated_config = McpConfigService.parse_mcp_config_dict(mcp_config)
148
+ elif isinstance(mcp_config, BaseMcpConfig):
149
+ validated_config = mcp_config
150
+ else:
151
+ raise TypeError(f"mcp_config must be a BaseMcpConfig object or a dictionary, not {type(mcp_config)}.")
152
+
153
+ logger.info(f"Previewing tools from MCP server: '{validated_config.server_id}'")
154
+ schema_mapper = McpSchemaMapper()
155
+ tool_definitions: List[ToolDefinition] = []
156
+
157
+ try:
158
+ remote_tools = await self._fetch_tools_from_server(validated_config)
159
+ logger.info(f"Discovered {len(remote_tools)} tools from server '{validated_config.server_id}' for preview.")
160
+ for remote_tool in remote_tools:
161
+ tool_def = self._create_tool_definition_from_remote(remote_tool, validated_config, schema_mapper)
162
+ tool_definitions.append(tool_def)
163
+ except Exception as e_server:
164
+ logger.error(f"Failed to discover tools for preview from MCP server '{validated_config.server_id}': {e_server}", exc_info=True)
165
+ raise
166
+
167
+ logger.info(f"MCP tool preview completed. Found {len(tool_definitions)} tools.")
168
+ return tool_definitions
169
+
170
+ def unregister_tools_from_server(self, server_id: str) -> bool:
171
+ if not self.is_server_registered(server_id):
172
+ return False
173
+ tools_to_unregister = self._registered_tools_by_server.pop(server_id, [])
174
+ for tool_def in tools_to_unregister:
175
+ self._tool_registry.unregister_tool(tool_def.name)
176
+ return True
177
+
178
+ def is_server_registered(self, server_id: str) -> bool:
179
+ return server_id in self._registered_tools_by_server
@@ -1,4 +1,4 @@
1
- # file: autobyteus/autobyteus/mcp/types.py
1
+ # file: autobyteus/autobyteus/tools/mcp/types.py
2
2
  import logging
3
3
  from typing import List, Dict, Any, Optional, Type
4
4
  from dataclasses import dataclass, field, InitVar
@@ -9,9 +9,17 @@ logger = logging.getLogger(__name__)
9
9
  class McpTransportType(str, Enum):
10
10
  """Enumeration of supported MCP transport types."""
11
11
  STDIO = "stdio"
12
- SSE = "sse"
13
12
  STREAMABLE_HTTP = "streamable_http"
14
13
 
14
+ @dataclass(frozen=True)
15
+ class McpServerInstanceKey:
16
+ """
17
+ A dedicated, hashable key for identifying a unique server instance.
18
+ An instance is unique for a given agent and a specific server configuration.
19
+ """
20
+ agent_id: str
21
+ server_id: str
22
+
15
23
  @dataclass
16
24
  class BaseMcpConfig:
17
25
  """
@@ -59,25 +67,6 @@ class StdioMcpServerConfig(BaseMcpConfig):
59
67
  if self.cwd is not None and not isinstance(self.cwd, str):
60
68
  raise ValueError(f"StdioMcpServerConfig '{self.server_id}' 'cwd' must be a string if provided.")
61
69
 
62
- @dataclass
63
- class SseMcpServerConfig(BaseMcpConfig):
64
- """Configuration parameters for an MCP server using SSE transport."""
65
- url: Optional[str] = None # Changed: Added default None
66
- token: Optional[str] = None
67
- headers: Dict[str, str] = field(default_factory=dict)
68
-
69
- def __post_init__(self):
70
- super().__post_init__()
71
- self.transport_type = McpTransportType.SSE
72
-
73
- if self.url is None or not isinstance(self.url, str) or not self.url.strip():
74
- raise ValueError(f"SseMcpServerConfig '{self.server_id}' 'url' must be a non-empty string.")
75
-
76
- if self.token is not None and not isinstance(self.token, str):
77
- raise ValueError(f"SseMcpServerConfig '{self.server_id}' 'token' must be a string if provided.")
78
- if not isinstance(self.headers, dict) or not all(isinstance(k, str) and isinstance(v, str) for k, v in self.headers.items()):
79
- raise ValueError(f"SseMcpServerConfig '{self.server_id}' 'headers' must be a Dict[str, str].")
80
-
81
70
  @dataclass
82
71
  class StreamableHttpMcpServerConfig(BaseMcpConfig):
83
72
  """Configuration parameters for an MCP server using Streamable HTTP transport."""
@@ -7,6 +7,7 @@ from datetime import datetime
7
7
  from typing import TYPE_CHECKING, Optional
8
8
 
9
9
  from autobyteus.tools import tool
10
+ from autobyteus.tools.tool_category import ToolCategory
10
11
  from autobyteus.utils.file_utils import get_default_download_folder
11
12
 
12
13
  if TYPE_CHECKING:
@@ -14,7 +15,7 @@ if TYPE_CHECKING:
14
15
 
15
16
  logger = logging.getLogger(__name__)
16
17
 
17
- @tool(name="PDFDownloader")
18
+ @tool(name="PDFDownloader", category=ToolCategory.WEB)
18
19
  async def pdf_downloader( # function name can be pdf_downloader
19
20
  context: 'AgentContext',
20
21
  url: str,
@@ -6,7 +6,7 @@ from typing import Dict, Any, List as TypingList, Type, TYPE_CHECKING, Optional,
6
6
  from autobyteus.llm.providers import LLMProvider
7
7
  from autobyteus.tools.tool_config import ToolConfig
8
8
  from autobyteus.tools.parameter_schema import ParameterSchema
9
- from autobyteus.tools.tool_category import ToolCategory
9
+ from autobyteus.tools.tool_origin import ToolOrigin
10
10
  from autobyteus.tools.usage.providers import (
11
11
  XmlSchemaProvider,
12
12
  JsonSchemaProvider,
@@ -28,10 +28,12 @@ class ToolDefinition:
28
28
  name: str,
29
29
  description: str,
30
30
  argument_schema: Optional['ParameterSchema'],
31
- category: ToolCategory,
31
+ origin: ToolOrigin,
32
+ category: str,
32
33
  config_schema: Optional['ParameterSchema'] = None,
33
34
  tool_class: Optional[Type['BaseTool']] = None,
34
- custom_factory: Optional[Callable[['ToolConfig'], 'BaseTool']] = None):
35
+ custom_factory: Optional[Callable[['ToolConfig'], 'BaseTool']] = None,
36
+ metadata: Optional[Dict[str, Any]] = None):
35
37
  """
36
38
  Initializes the ToolDefinition.
37
39
  """
@@ -54,8 +56,12 @@ class ToolDefinition:
54
56
  raise TypeError(f"ToolDefinition '{name}' received an invalid 'argument_schema'. Expected ParameterSchema or None.")
55
57
  if config_schema is not None and not isinstance(config_schema, ParameterSchema):
56
58
  raise TypeError(f"ToolDefinition '{name}' received an invalid 'config_schema'. Expected ParameterSchema or None.")
57
- if not isinstance(category, ToolCategory):
58
- raise TypeError(f"ToolDefinition '{name}' requires a ToolCategory for 'category'. Got {type(category)}")
59
+ if not isinstance(origin, ToolOrigin):
60
+ raise TypeError(f"ToolDefinition '{name}' requires a ToolOrigin for 'origin'. Got {type(origin)}")
61
+
62
+ # Validation for MCP-specific metadata
63
+ if origin == ToolOrigin.MCP and not (metadata and metadata.get("mcp_server_id")):
64
+ raise ValueError(f"ToolDefinition '{name}' with origin MCP must provide a 'mcp_server_id' in its metadata.")
59
65
 
60
66
  self._name = name
61
67
  self._description = description
@@ -63,7 +69,9 @@ class ToolDefinition:
63
69
  self._config_schema: Optional['ParameterSchema'] = config_schema
64
70
  self._tool_class = tool_class
65
71
  self._custom_factory = custom_factory
72
+ self._origin = origin
66
73
  self._category = category
74
+ self._metadata = metadata or {}
67
75
 
68
76
  logger.debug(f"ToolDefinition created for tool '{self.name}'.")
69
77
 
@@ -81,7 +89,11 @@ class ToolDefinition:
81
89
  @property
82
90
  def config_schema(self) -> Optional['ParameterSchema']: return self._config_schema
83
91
  @property
84
- def category(self) -> ToolCategory: return self._category
92
+ def origin(self) -> ToolOrigin: return self._origin
93
+ @property
94
+ def category(self) -> str: return self._category
95
+ @property
96
+ def metadata(self) -> Dict[str, Any]: return self._metadata
85
97
 
86
98
  # --- Schema Generation API ---
87
99
  def get_usage_xml(self, provider: Optional[LLMProvider] = None) -> str:
@@ -136,4 +148,5 @@ class ToolDefinition:
136
148
 
137
149
  def __repr__(self) -> str:
138
150
  creator_repr = f"class='{self._tool_class.__name__}'" if self._tool_class else "factory=True"
139
- return (f"ToolDefinition(name='{self.name}', category='{self.category.value}', {creator_repr})")
151
+ metadata_repr = f", metadata={self.metadata}" if self.metadata else ""
152
+ return (f"ToolDefinition(name='{self.name}', origin='{self.origin.value}', category='{self.category}'{metadata_repr}, {creator_repr})")
@@ -1,10 +1,12 @@
1
1
  # file: autobyteus/tools/registry/tool_registry.py
2
2
  import logging
3
3
  from typing import Dict, List, Optional, Type, TYPE_CHECKING
4
+ from collections import defaultdict
4
5
 
5
6
  from autobyteus.tools.registry.tool_definition import ToolDefinition
6
7
  from autobyteus.utils.singleton import SingletonMeta
7
8
  from autobyteus.tools.tool_config import ToolConfig
9
+ from autobyteus.tools.tool_origin import ToolOrigin
8
10
 
9
11
  if TYPE_CHECKING:
10
12
  from autobyteus.tools.base_tool import BaseTool
@@ -78,19 +80,8 @@ class ToolRegistry(metaclass=SingletonMeta):
78
80
 
79
81
  def create_tool(self, name: str, config: Optional[ToolConfig] = None) -> 'BaseTool':
80
82
  """
81
- Creates a tool instance using its definition, either from a factory or a class.
82
-
83
- Args:
84
- name: The name of the tool to create.
85
- config: Optional ToolConfig with constructor parameters for class-based tools
86
- or to be passed to a custom factory.
87
-
88
- Returns:
89
- The tool instance if the definition exists.
90
-
91
- Raises:
92
- ValueError: If the tool definition is not found or is invalid.
93
- TypeError: If tool instantiation fails.
83
+ Creates a tool instance using its definition and injects the definition
84
+ back into the instance.
94
85
  """
95
86
  definition = self.get_tool_definition(name)
96
87
  if not definition:
@@ -98,24 +89,20 @@ class ToolRegistry(metaclass=SingletonMeta):
98
89
  raise ValueError(f"No tool definition found for name '{name}'")
99
90
 
100
91
  try:
101
- # Prefer the custom factory if it exists
92
+ tool_instance: 'BaseTool'
102
93
  if definition.custom_factory:
103
94
  logger.info(f"Creating tool instance for '{name}' using its custom factory.")
104
- # Pass the config to the factory. The factory can choose to use it or not.
105
95
  tool_instance = definition.custom_factory(config)
106
-
107
- # Fall back to instantiating the tool_class
108
96
  elif definition.tool_class:
109
- # For class-based tools, the convention is to pass the ToolConfig object
110
- # itself to the constructor under the 'config' keyword argument.
111
97
  logger.info(f"Creating tool instance for '{name}' using class '{definition.tool_class.__name__}' and passing ToolConfig.")
112
98
  tool_instance = definition.tool_class(config=config)
113
-
114
99
  else:
115
- # This case should be prevented by ToolDefinition's validation
116
100
  raise ValueError(f"ToolDefinition for '{name}' is invalid: missing both tool_class and custom_factory.")
117
101
 
118
- logger.debug(f"Successfully created tool instance for '{name}'")
102
+ # Inject the definition into the newly created instance.
103
+ tool_instance.definition = definition
104
+ logger.debug(f"Injected ToolDefinition into instance of '{name}'.")
105
+
119
106
  return tool_instance
120
107
 
121
108
  except Exception as e:
@@ -126,23 +113,83 @@ class ToolRegistry(metaclass=SingletonMeta):
126
113
  def list_tools(self) -> List[ToolDefinition]:
127
114
  """
128
115
  Returns a list of all registered tool definitions.
129
-
130
- Returns:
131
- A list of ToolDefinition objects.
132
116
  """
133
117
  return list(self._definitions.values())
134
118
 
135
119
  def list_tool_names(self) -> List[str]:
136
120
  """
137
121
  Returns a list of the names of all registered tools.
138
-
139
- Returns:
140
- A list of tool name strings.
141
122
  """
142
123
  return list(self._definitions.keys())
143
124
 
144
125
  def get_all_definitions(self) -> Dict[str, ToolDefinition]:
145
126
  """Returns the internal dictionary of definitions."""
146
127
  return dict(ToolRegistry._definitions)
128
+
129
+ def get_tools_by_mcp_server(self, server_id: str) -> List[ToolDefinition]:
130
+ """
131
+ Returns a list of all registered tool definitions that originated
132
+ from a specific MCP server.
133
+
134
+ Args:
135
+ server_id: The unique ID of the MCP server to query for.
136
+
137
+ Returns:
138
+ A list of matching ToolDefinition objects.
139
+ """
140
+ if not server_id:
141
+ return []
142
+
143
+ return [
144
+ td for td in self._definitions.values()
145
+ if td.origin == ToolOrigin.MCP and td.metadata.get("mcp_server_id") == server_id
146
+ ]
147
+
148
+ def get_tools_by_category(self, category: str) -> List[ToolDefinition]:
149
+ """
150
+ Returns a list of all registered tool definitions that match a specific category.
151
+
152
+ Args:
153
+ category: The category string to filter by.
154
+
155
+ Returns:
156
+ A list of matching ToolDefinition objects, sorted by name.
157
+ """
158
+ if not category:
159
+ return []
160
+
161
+ matching_tools = [
162
+ td for td in self._definitions.values() if td.category == category
163
+ ]
164
+ return sorted(matching_tools, key=lambda td: td.name)
165
+
166
+ def get_tools_grouped_by_category(self, origin: Optional[ToolOrigin] = None) -> Dict[str, List[ToolDefinition]]:
167
+ """
168
+ Returns all registered tool definitions, grouped into a dictionary by their category.
169
+ Can optionally filter by tool origin before grouping.
170
+
171
+ Args:
172
+ origin: If provided, only tools from this origin will be included.
173
+
174
+ Returns:
175
+ A dictionary where keys are category strings and values are lists
176
+ of ToolDefinition objects belonging to that category. Both the categories
177
+ and the tools within each category are sorted alphabetically.
178
+ """
179
+ grouped_tools = defaultdict(list)
180
+
181
+ tools_to_process = self._definitions.values()
182
+ if origin:
183
+ tools_to_process = [td for td in tools_to_process if td.origin == origin]
184
+
185
+ for td in tools_to_process:
186
+ grouped_tools[td.category].append(td)
187
+
188
+ # Sort tools within each category and sort the categories themselves for deterministic output
189
+ sorted_grouped_tools = {}
190
+ for category in sorted(grouped_tools.keys()):
191
+ sorted_grouped_tools[category] = sorted(grouped_tools[category], key=lambda td: td.name)
192
+
193
+ return sorted_grouped_tools
147
194
 
148
195
  default_tool_registry = ToolRegistry()
autobyteus/tools/timer.py CHANGED
@@ -3,6 +3,7 @@ from typing import Optional, TYPE_CHECKING, Any
3
3
  from autobyteus.tools.base_tool import BaseTool
4
4
  from autobyteus.tools.tool_config import ToolConfig
5
5
  from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
6
+ from autobyteus.tools.tool_category import ToolCategory
6
7
  from autobyteus.events.event_emitter import EventEmitter
7
8
  from autobyteus.events.event_types import EventType
8
9
  import logging
@@ -17,6 +18,7 @@ class Timer(BaseTool, EventEmitter):
17
18
  A tool that provides timer functionality with configurable duration and event emission.
18
19
  The timer runs independently after being started and emits TIMER_UPDATE events.
19
20
  """
21
+ CATEGORY = ToolCategory.UTILITY
20
22
 
21
23
  def __init__(self, config: Optional[ToolConfig] = None):
22
24
  BaseTool.__init__(self, config=config)
@@ -2,10 +2,20 @@
2
2
  from enum import Enum
3
3
 
4
4
  class ToolCategory(str, Enum):
5
- """Enumeration of tool categories to identify their origin."""
6
- LOCAL = "local"
7
- MCP = "mcp"
8
- # BUILT_IN, USER_DEFINED etc. could be added later.
5
+ """
6
+ Provides standardized string constants for common tool categories.
7
+ While tools can use any string for a category, using these constants
8
+ is recommended to ensure consistency in UI grouping.
9
+ """
10
+ USER_INTERACTION = "User Interaction"
11
+ FILE_SYSTEM = "File System"
12
+ WEB = "Web"
13
+ SYSTEM = "System"
14
+ UTILITY = "Utility"
15
+ AGENT_COMMUNICATION = "Agent Communication"
16
+ PROMPT_MANAGEMENT = "Prompt Management"
17
+ GENERAL = "General"
18
+ MCP = "MCP"
9
19
 
10
20
  def __str__(self) -> str:
11
21
  return self.value
@@ -5,6 +5,7 @@ from typing import Dict, Any
5
5
 
6
6
  from autobyteus.tools.registry import default_tool_registry, ToolDefinition
7
7
  from autobyteus.tools.parameter_schema import ParameterSchema
8
+ from autobyteus.tools.tool_origin import ToolOrigin
8
9
  from autobyteus.tools.tool_category import ToolCategory
9
10
 
10
11
  logger = logging.getLogger(__name__)
@@ -53,6 +54,9 @@ class ToolMeta(ABCMeta):
53
54
  instantiation_config_schema = None
54
55
  except Exception as e:
55
56
  logger.warning(f"Tool class {name} ({tool_name}) has get_config_schema() but it failed: {e}. Assuming no instantiation config.")
57
+
58
+ # Get category from class attribute, defaulting to "General"
59
+ category_str = getattr(cls, 'CATEGORY', ToolCategory.GENERAL)
56
60
 
57
61
  # Create the definition without pre-generating usage strings
58
62
  definition = ToolDefinition(
@@ -62,7 +66,8 @@ class ToolMeta(ABCMeta):
62
66
  custom_factory=None,
63
67
  argument_schema=argument_schema,
64
68
  config_schema=instantiation_config_schema,
65
- category=ToolCategory.LOCAL
69
+ origin=ToolOrigin.LOCAL,
70
+ category=category_str
66
71
  )
67
72
  default_tool_registry.register_tool(definition)
68
73
 
@@ -0,0 +1,10 @@
1
+ # file: autobyteus/autobyteus/tools/tool_origin.py
2
+ from enum import Enum
3
+
4
+ class ToolOrigin(str, Enum):
5
+ """Enumeration of tool origins to identify their execution model."""
6
+ LOCAL = "local"
7
+ MCP = "mcp"
8
+
9
+ def __str__(self) -> str:
10
+ return self.value
@@ -0,0 +1,93 @@
1
+ # file: autobyteus/autobyteus/workflow/agentic_workflow.py
2
+ import logging
3
+ from typing import Optional
4
+
5
+ from autobyteus.workflow.runtime.workflow_runtime import WorkflowRuntime
6
+ from autobyteus.workflow.events.workflow_events import ProcessUserMessageEvent, ToolApprovalWorkflowEvent
7
+ from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
8
+ from autobyteus.workflow.phases.workflow_operational_phase import WorkflowOperationalPhase
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class AgenticWorkflow:
13
+ """
14
+ User-facing facade for interacting with a managed workflow.
15
+ This class is a lightweight wrapper around a WorkflowRuntime instance
16
+ and is typically created by a WorkflowFactory.
17
+ """
18
+ def __init__(self, runtime: WorkflowRuntime):
19
+ """
20
+ Initializes the AgenticWorkflow facade.
21
+
22
+ Args:
23
+ runtime: The pre-configured and ready-to-use runtime for the workflow.
24
+ """
25
+ if not isinstance(runtime, WorkflowRuntime):
26
+ raise TypeError(f"AgenticWorkflow requires a WorkflowRuntime instance, got {type(runtime).__name__}")
27
+
28
+ self._runtime = runtime
29
+ self.workflow_id: str = self._runtime.context.workflow_id
30
+ logger.info(f"AgenticWorkflow facade created for workflow ID '{self.workflow_id}'.")
31
+
32
+ @property
33
+ def name(self) -> str:
34
+ return self._runtime.context.config.name
35
+
36
+ @property
37
+ def role(self) -> Optional[str]:
38
+ """The role of the workflow, for when it's used as a sub-workflow."""
39
+ return self._runtime.context.config.role
40
+
41
+ async def post_message(self, message: AgentInputUserMessage, target_agent_name: Optional[str] = None) -> None:
42
+ """
43
+ Submits a message to the workflow, routing it to a specific node (agent or sub-workflow).
44
+ If `target_agent_name` is not provided, the message is sent to the workflow's coordinator.
45
+ """
46
+ final_target_name = target_agent_name or self._runtime.context.config.coordinator_node.name
47
+ logger.info(f"Workflow '{self.workflow_id}': post_message called. Target: '{final_target_name}'.")
48
+
49
+ if not self._runtime.is_running:
50
+ self.start()
51
+
52
+ event = ProcessUserMessageEvent(
53
+ user_message=message,
54
+ target_agent_name=final_target_name
55
+ )
56
+ await self._runtime.submit_event(event)
57
+
58
+ async def post_tool_execution_approval(
59
+ self,
60
+ agent_name: str,
61
+ tool_invocation_id: str,
62
+ is_approved: bool,
63
+ reason: Optional[str] = None
64
+ ):
65
+ """Submits a tool execution approval/denial to a specific agent in the workflow."""
66
+ logger.info(f"Workflow '{self.workflow_id}': post_tool_execution_approval called for agent '{agent_name}'. Approved: {is_approved}.")
67
+ if not self._runtime.is_running:
68
+ logger.warning(f"Workflow '{self.workflow_id}' is not running. Cannot post approval.")
69
+ return
70
+
71
+ event = ToolApprovalWorkflowEvent(
72
+ agent_name=agent_name,
73
+ tool_invocation_id=tool_invocation_id,
74
+ is_approved=is_approved,
75
+ reason=reason,
76
+ )
77
+ await self._runtime.submit_event(event)
78
+
79
+ def start(self) -> None:
80
+ """Starts the workflow's background worker thread."""
81
+ self._runtime.start()
82
+
83
+ async def stop(self, timeout: float = 10.0) -> None:
84
+ """Stops the workflow and all its agents."""
85
+ await self._runtime.stop(timeout)
86
+
87
+ @property
88
+ def is_running(self) -> bool:
89
+ """Checks if the workflow's worker is running."""
90
+ return self._runtime.is_running
91
+
92
+ def get_current_phase(self) -> WorkflowOperationalPhase:
93
+ return self._runtime.context.state.current_phase