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,37 @@
1
+ import logging
2
+ from autobyteus.llm.models import LLMModel
3
+ from autobyteus.llm.utils.llm_config import LLMConfig
4
+ from autobyteus.llm.api.openai_compatible_llm import OpenAICompatibleLLM
5
+ import os
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ class LMStudioLLM(OpenAICompatibleLLM):
10
+ """
11
+ LLM class for models served by a local LM Studio instance.
12
+
13
+ This class communicates with an LM Studio server, which exposes an OpenAI-compatible API.
14
+ It expects the LM Studio server to be running at the address specified by the `LMSTUDIO_HOST`
15
+ environment variable, or at `http://localhost:1234` by default.
16
+
17
+ Note: The LM Studio server does not require a real API key. A dummy key "lm-studio" is used
18
+ by default. If you need to use a different key, you can set the `LMSTUDIO_API_KEY`
19
+ environment variable.
20
+ """
21
+ DEFAULT_LMSTUDIO_HOST = 'http://localhost:1234'
22
+
23
+ def __init__(self, model: LLMModel, llm_config: LLMConfig):
24
+ lmstudio_host = os.getenv('LMSTUDIO_HOST', self.DEFAULT_LMSTUDIO_HOST)
25
+ base_url = f"{lmstudio_host}/v1"
26
+
27
+ super().__init__(
28
+ model=model,
29
+ llm_config=llm_config,
30
+ api_key_env_var="LMSTUDIO_API_KEY",
31
+ base_url=base_url,
32
+ api_key_default="lm-studio"
33
+ )
34
+ logger.info(f"LMStudioLLM initialized with model: {self.model.name} and base URL: {base_url}")
35
+
36
+ async def cleanup(self):
37
+ await super().cleanup()
@@ -21,12 +21,29 @@ class OpenAICompatibleLLM(BaseLLM, ABC):
21
21
  model: LLMModel,
22
22
  llm_config: LLMConfig,
23
23
  api_key_env_var: str,
24
- base_url: str
24
+ base_url: str,
25
+ api_key_default: Optional[str] = None
25
26
  ):
27
+ """
28
+ Initializes an OpenAI-compatible LLM.
29
+
30
+ Args:
31
+ model (LLMModel): The model to use.
32
+ llm_config (LLMConfig): Configuration for the LLM.
33
+ api_key_env_var (str): The name of the environment variable for the API key.
34
+ base_url (str): The base URL for the API.
35
+ api_key_default (Optional[str], optional): A default API key to use if the
36
+ environment variable is not set.
37
+ Defaults to None.
38
+ """
26
39
  api_key = os.getenv(api_key_env_var)
27
40
  if not api_key:
28
- logger.error(f"{api_key_env_var} environment variable is not set.")
29
- raise ValueError(f"{api_key_env_var} environment variable is not set.")
41
+ if api_key_default:
42
+ api_key = api_key_default
43
+ logger.info(f"{api_key_env_var} not set, using default key.")
44
+ else:
45
+ logger.error(f"{api_key_env_var} environment variable is not set.")
46
+ raise ValueError(f"{api_key_env_var} environment variable is not set.")
30
47
 
31
48
  self.client = OpenAI(api_key=api_key, base_url=base_url)
32
49
  logger.info(f"Initialized OpenAI compatible client with base_url: {base_url}")
@@ -16,6 +16,7 @@ from autobyteus.llm.api.deepseek_llm import DeepSeekLLM
16
16
  from autobyteus.llm.api.grok_llm import GrokLLM
17
17
  from autobyteus.llm.api.kimi_llm import KimiLLM
18
18
  from autobyteus.llm.ollama_provider import OllamaModelProvider
19
+ from autobyteus.llm.lmstudio_provider import LMStudioModelProvider
19
20
  from autobyteus.utils.singleton import SingletonMeta
20
21
 
21
22
  logger = logging.getLogger(__name__)
@@ -307,6 +308,7 @@ class LLMFactory(metaclass=SingletonMeta):
307
308
 
308
309
  OllamaModelProvider.discover_and_register()
309
310
  AutobyteusModelProvider.discover_and_register()
311
+ LMStudioModelProvider.discover_and_register()
310
312
 
311
313
  @staticmethod
312
314
  def register_model(model: LLMModel):
@@ -0,0 +1,89 @@
1
+ from autobyteus.llm.models import LLMModel
2
+ from autobyteus.llm.api.lmstudio_llm import LMStudioLLM
3
+ from autobyteus.llm.providers import LLMProvider
4
+ from autobyteus.llm.utils.llm_config import LLMConfig, TokenPricingConfig
5
+ from typing import TYPE_CHECKING
6
+ import os
7
+ import logging
8
+ from openai import OpenAI, APIConnectionError, OpenAIError
9
+ from urllib.parse import urlparse
10
+
11
+ if TYPE_CHECKING:
12
+ from autobyteus.llm.llm_factory import LLMFactory
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class LMStudioModelProvider:
17
+ DEFAULT_LMSTUDIO_HOST = 'http://localhost:1234'
18
+
19
+ @staticmethod
20
+ def is_valid_url(url: str) -> bool:
21
+ """Validate if the provided URL is properly formatted."""
22
+ try:
23
+ result = urlparse(url)
24
+ return all([result.scheme, result.netloc])
25
+ except Exception:
26
+ return False
27
+
28
+ @staticmethod
29
+ def discover_and_register():
30
+ """
31
+ Discovers models from a local LM Studio instance and registers them with the LLMFactory.
32
+ """
33
+ try:
34
+ from autobyteus.llm.llm_factory import LLMFactory
35
+
36
+ lmstudio_host = os.getenv('LMSTUDIO_HOST', LMStudioModelProvider.DEFAULT_LMSTUDIO_HOST)
37
+
38
+ if not LMStudioModelProvider.is_valid_url(lmstudio_host):
39
+ logger.error(f"Invalid LM Studio host URL: {lmstudio_host}")
40
+ return
41
+
42
+ base_url = f"{lmstudio_host}/v1"
43
+
44
+ # Use a dummy API key for initialization. LM Studio doesn't require one.
45
+ client = OpenAI(base_url=base_url, api_key="lm-studio")
46
+
47
+ try:
48
+ response = client.models.list()
49
+ models = response.data
50
+ except APIConnectionError as e:
51
+ logger.warning(
52
+ f"Could not connect to LM Studio server at {base_url}. "
53
+ "Please ensure LM Studio is running with the server started. "
54
+ f"Error: {e.__cause__}"
55
+ )
56
+ return
57
+ except OpenAIError as e:
58
+ logger.error(f"An error occurred while fetching models from LM Studio: {e}")
59
+ return
60
+
61
+ registered_count = 0
62
+ for model_info in models:
63
+ model_id = model_info.id
64
+ if not model_id:
65
+ continue
66
+
67
+ try:
68
+ llm_model = LLMModel(
69
+ name=model_id,
70
+ value=model_id,
71
+ provider=LLMProvider.LMSTUDIO,
72
+ llm_class=LMStudioLLM,
73
+ canonical_name=model_id,
74
+ default_config=LLMConfig(
75
+ rate_limit=None, # No rate limit for local models by default
76
+ token_limit=8192, # A reasonable default
77
+ pricing_config=TokenPricingConfig(0.0, 0.0) # Local models are free
78
+ )
79
+ )
80
+ LLMFactory.register_model(llm_model)
81
+ registered_count += 1
82
+ except Exception as e:
83
+ logger.warning(f"Failed to register LM Studio model {model_id}: {str(e)}")
84
+
85
+ if registered_count > 0:
86
+ logger.info(f"Successfully registered {registered_count} LM Studio models from {lmstudio_host}")
87
+
88
+ except Exception as e:
89
+ logger.error(f"Unexpected error during LM Studio model discovery: {str(e)}")
@@ -13,3 +13,4 @@ class LLMProvider(Enum):
13
13
  GROK = "grok"
14
14
  AUTOBYTEUS = "autobyteus"
15
15
  KIMI = "kimi"
16
+ LMSTUDIO = "lmstudio"
@@ -36,6 +36,8 @@ def get_token_counter(model: LLMModel, llm: 'BaseLLM') -> BaseTokenCounter:
36
36
  return KimiTokenCounter(model, llm)
37
37
  elif model.provider == LLMProvider.OLLAMA:
38
38
  return OpenAITokenCounter(model, llm)
39
+ elif model.provider == LLMProvider.LMSTUDIO:
40
+ return OpenAITokenCounter(model, llm)
39
41
  elif model.provider == LLMProvider.GEMINI:
40
42
  return OpenAITokenCounter(model, llm)
41
43
  else:
@@ -10,6 +10,7 @@ from .base_tool import BaseTool
10
10
  from .functional_tool import tool # The @tool decorator
11
11
  from .parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
12
12
  from .tool_config import ToolConfig # Configuration data object, primarily for class-based tools
13
+ from .tool_origin import ToolOrigin
13
14
  from .tool_category import ToolCategory
14
15
 
15
16
  # --- Re-export specific tools for easier access ---
@@ -48,6 +49,7 @@ __all__ = [
48
49
  "ParameterDefinition",
49
50
  "ParameterType",
50
51
  "ToolConfig",
52
+ "ToolOrigin",
51
53
  "ToolCategory",
52
54
 
53
55
  # Re-exported functional tool instances
@@ -3,13 +3,14 @@ import logging
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from autobyteus.tools import tool # Main @tool decorator
6
+ from autobyteus.tools.tool_category import ToolCategory
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from autobyteus.agent.context import AgentContext
9
10
 
10
11
  logger = logging.getLogger(__name__)
11
12
 
12
- @tool(name="AskUserInput") # Explicit name matching the old class name
13
+ @tool(name="AskUserInput", category=ToolCategory.USER_INTERACTION)
13
14
  async def ask_user_input(context: 'AgentContext', request: str) -> str: # Function name can be ask_user_input
14
15
  """
15
16
  Requests input from the user based on a given prompt and returns the user's textual response.
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
16
16
  from autobyteus.tools.parameter_schema import ParameterSchema
17
17
  from autobyteus.tools.tool_config import ToolConfig
18
18
  from .tool_state import ToolState
19
+ from autobyteus.tools.registry import ToolDefinition
19
20
 
20
21
  logger = logging.getLogger('autobyteus')
21
22
 
@@ -26,6 +27,7 @@ class BaseTool(ABC, EventEmitter, metaclass=ToolMeta):
26
27
  def __init__(self, config: Optional['ToolConfig'] = None):
27
28
  super().__init__()
28
29
  self.agent_id: Optional[str] = None
30
+ self.definition: Optional['ToolDefinition'] = None # Link back to its definition
29
31
  # The config is stored primarily for potential use by subclasses or future base features.
30
32
  self._config = config
31
33
  # Add a dedicated state dictionary for the tool instance
@@ -4,13 +4,14 @@ import logging
4
4
  from typing import TYPE_CHECKING, Optional
5
5
 
6
6
  from autobyteus.tools import tool
7
+ from autobyteus.tools.tool_category import ToolCategory
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
- @tool(name="BashExecutor")
14
+ @tool(name="BashExecutor", category=ToolCategory.SYSTEM)
14
15
  async def bash_executor(context: Optional['AgentContext'], command: str, cwd: Optional[str] = None) -> str:
15
16
  """
16
17
  Executes bash commands and retrieves their standard output.
@@ -1,6 +1,7 @@
1
1
  from autobyteus.tools.browser.session_aware.browser_session_aware_tool import BrowserSessionAwareTool
2
2
  from autobyteus.tools.browser.session_aware.shared_browser_session import SharedBrowserSession
3
3
  from autobyteus.tools.tool_config import ToolConfig
4
+ from autobyteus.tools.tool_category import ToolCategory
4
5
  from urllib.parse import urlparse
5
6
  from typing import Optional, TYPE_CHECKING, Any
6
7
  import logging
@@ -16,6 +17,7 @@ class BrowserSessionAwareNavigateTo(BrowserSessionAwareTool):
16
17
  """
17
18
  A session-aware tool for navigating to a specified website using a shared browser session.
18
19
  """
20
+ CATEGORY = ToolCategory.WEB
19
21
 
20
22
  def __init__(self, config: Optional[ToolConfig] = None):
21
23
  super().__init__(config=config)
@@ -9,6 +9,7 @@ from autobyteus.tools.browser.session_aware.shared_browser_session import Shared
9
9
  from autobyteus.tools.browser.session_aware.web_element_action import WebElementAction
10
10
  from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
11
11
  from autobyteus.tools.tool_config import ToolConfig
12
+ from autobyteus.tools.tool_category import ToolCategory
12
13
 
13
14
  if TYPE_CHECKING:
14
15
  pass
@@ -19,6 +20,8 @@ class BrowserSessionAwareWebElementTrigger(BrowserSessionAwareTool):
19
20
  """
20
21
  A session-aware tool to trigger actions on web elements identified by a CSS selector.
21
22
  """
23
+ CATEGORY = ToolCategory.WEB
24
+
22
25
  def __init__(self, config: Optional[ToolConfig] = None):
23
26
  super().__init__(config=config)
24
27
  logger.debug("BrowserSessionAwareWebElementTrigger tool initialized.")
@@ -6,6 +6,7 @@ from autobyteus.tools.browser.session_aware.browser_session_aware_tool import Br
6
6
  from autobyteus.tools.browser.session_aware.shared_browser_session import SharedBrowserSession
7
7
  from autobyteus.tools.tool_config import ToolConfig
8
8
  from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
9
+ from autobyteus.tools.tool_category import ToolCategory
9
10
  from autobyteus.utils.html_cleaner import clean, CleaningMode
10
11
 
11
12
  if TYPE_CHECKING:
@@ -18,6 +19,8 @@ class BrowserSessionAwareWebPageReader(BrowserSessionAwareTool):
18
19
  A session-aware tool to read and clean HTML content from the current page
19
20
  in a shared browser session.
20
21
  """
22
+ CATEGORY = ToolCategory.WEB
23
+
21
24
  def __init__(self, config: Optional[ToolConfig] = None):
22
25
  super().__init__(config=config)
23
26
 
@@ -7,6 +7,7 @@ from autobyteus.tools.browser.session_aware.browser_session_aware_tool import Br
7
7
  from autobyteus.tools.browser.session_aware.shared_browser_session import SharedBrowserSession
8
8
  from autobyteus.tools.tool_config import ToolConfig
9
9
  from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
+ from autobyteus.tools.tool_category import ToolCategory
10
11
 
11
12
  if TYPE_CHECKING:
12
13
  from autobyteus.agent.context import AgentContext
@@ -17,6 +18,8 @@ class BrowserSessionAwareWebPageScreenshotTaker(BrowserSessionAwareTool):
17
18
  """
18
19
  A session-aware tool to take a screenshot of the current page in a shared browser session.
19
20
  """
21
+ CATEGORY = ToolCategory.WEB
22
+
20
23
  def __init__(self, config: Optional[ToolConfig] = None):
21
24
  super().__init__(config=config)
22
25
 
@@ -9,6 +9,7 @@ from typing import Optional, TYPE_CHECKING, Any
9
9
  from autobyteus.tools.base_tool import BaseTool
10
10
  from autobyteus.tools.tool_config import ToolConfig
11
11
  from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
12
+ from autobyteus.tools.tool_category import ToolCategory
12
13
  from brui_core.ui_integrator import UIIntegrator
13
14
  from autobyteus.utils.html_cleaner import clean, CleaningMode
14
15
 
@@ -22,6 +23,7 @@ class GoogleSearch(BaseTool, UIIntegrator): # Multiple inheritance
22
23
  A tool that allows for performing a Google search using Playwright and retrieving the search results.
23
24
  Inherits from BaseTool for tool framework compatibility and UIIntegrator for Playwright integration.
24
25
  """
26
+ CATEGORY = ToolCategory.WEB
25
27
 
26
28
  def __init__(self, config: Optional[ToolConfig] = None):
27
29
  BaseTool.__init__(self, config=config)
@@ -1,5 +1,6 @@
1
1
  from autobyteus.tools.base_tool import BaseTool
2
2
  from autobyteus.tools.tool_config import ToolConfig
3
+ from autobyteus.tools.tool_category import ToolCategory
3
4
  from brui_core.ui_integrator import UIIntegrator
4
5
  from urllib.parse import urlparse
5
6
  from typing import Optional, TYPE_CHECKING, Any
@@ -17,6 +18,7 @@ class NavigateTo(BaseTool, UIIntegrator):
17
18
  A standalone tool for navigating to a specified website using Playwright.
18
19
  It initializes and closes its own browser instance for each navigation.
19
20
  """
21
+ CATEGORY = ToolCategory.WEB
20
22
 
21
23
  def __init__(self, config: Optional[ToolConfig] = None):
22
24
  BaseTool.__init__(self, config=config)
@@ -1,5 +1,6 @@
1
1
  from autobyteus.tools.base_tool import BaseTool
2
2
  from autobyteus.tools.tool_config import ToolConfig
3
+ from autobyteus.tools.tool_category import ToolCategory
3
4
  from brui_core.ui_integrator import UIIntegrator
4
5
  import os
5
6
  import logging
@@ -18,6 +19,8 @@ class WebPagePDFGenerator(BaseTool, UIIntegrator):
18
19
  A class that generates a PDF of a given webpage URL using Playwright.
19
20
  Saves the PDF to a specified directory. This is a standalone browser tool.
20
21
  """
22
+ CATEGORY = ToolCategory.WEB
23
+
21
24
  def __init__(self, config: Optional[ToolConfig] = None):
22
25
  BaseTool.__init__(self, config=config)
23
26
  UIIntegrator.__init__(self)
@@ -1,5 +1,6 @@
1
1
  from autobyteus.tools.base_tool import BaseTool
2
2
  from autobyteus.tools.tool_config import ToolConfig
3
+ from autobyteus.tools.tool_category import ToolCategory
3
4
  from brui_core.ui_integrator import UIIntegrator
4
5
  import os
5
6
  import logging
@@ -18,6 +19,8 @@ class WebPageImageDownloader(BaseTool, UIIntegrator):
18
19
  A class that downloads images (excluding SVGs and data URIs) from a given webpage URL using Playwright.
19
20
  Saves images to a specified directory.
20
21
  """
22
+ CATEGORY = ToolCategory.WEB
23
+
21
24
  def __init__(self, config: Optional[ToolConfig] = None):
22
25
  BaseTool.__init__(self, config=config)
23
26
  UIIntegrator.__init__(self)
@@ -8,6 +8,7 @@ from typing import Optional, TYPE_CHECKING, Any
8
8
  from autobyteus.tools.base_tool import BaseTool
9
9
  from autobyteus.tools.tool_config import ToolConfig
10
10
  from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
11
+ from autobyteus.tools.tool_category import ToolCategory
11
12
  from brui_core.ui_integrator import UIIntegrator
12
13
  from autobyteus.utils.html_cleaner import clean, CleaningMode
13
14
 
@@ -20,6 +21,7 @@ class WebPageReader(BaseTool, UIIntegrator):
20
21
  """
21
22
  A class that reads and cleans the HTML content from a given webpage using Playwright.
22
23
  """
24
+ CATEGORY = ToolCategory.WEB
23
25
 
24
26
  def __init__(self, config: Optional[ToolConfig] = None):
25
27
  BaseTool.__init__(self, config=config)
@@ -2,6 +2,7 @@ from typing import Optional, TYPE_CHECKING, Any
2
2
  from autobyteus.tools.base_tool import BaseTool
3
3
  from autobyteus.tools.tool_config import ToolConfig
4
4
  from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
5
+ from autobyteus.tools.tool_category import ToolCategory
5
6
  from brui_core.ui_integrator import UIIntegrator
6
7
  import logging
7
8
  import os
@@ -15,6 +16,8 @@ class WebPageScreenshotTaker(BaseTool, UIIntegrator):
15
16
  """
16
17
  A class that takes a screenshot of a given webpage using Playwright and saves it.
17
18
  """
19
+ CATEGORY = ToolCategory.WEB
20
+
18
21
  def __init__(self, config: Optional[ToolConfig] = None):
19
22
  BaseTool.__init__(self, config=config)
20
23
  UIIntegrator.__init__(self)
@@ -3,27 +3,54 @@ import logging
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from autobyteus.tools import tool
6
+ from autobyteus.tools.tool_category import ToolCategory
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from autobyteus.agent.context import AgentContext
9
10
 
10
11
  logger = logging.getLogger(__name__)
11
12
 
12
- @tool(name="FileReader") # Keep registered name "FileReader"
13
- async def file_reader(context: 'AgentContext', path: str) -> str: # function name can be same
13
+ @tool(name="FileReader", category=ToolCategory.FILE_SYSTEM)
14
+ async def file_reader(context: 'AgentContext', path: str) -> str:
14
15
  """
15
16
  Reads content from a specified file.
16
- 'path' is the absolute or relative path to the file.
17
+ 'path' is the path to the file. If relative, it must be resolved against a configured agent workspace.
18
+ Raises ValueError if a relative path is given without a valid workspace.
17
19
  Raises FileNotFoundError if the file does not exist.
18
20
  Raises IOError if file reading fails for other reasons.
19
21
  """
20
- logger.debug(f"Functional FileReader tool for agent {context.agent_id}, path: {path}")
21
- if not os.path.exists(path):
22
- raise FileNotFoundError(f"The file at {path} does not exist.")
22
+ logger.debug(f"Functional FileReader tool for agent {context.agent_id}, initial path: {path}")
23
+
24
+ final_path: str
25
+ if os.path.isabs(path):
26
+ final_path = path
27
+ logger.debug(f"Path '{path}' is absolute. Using it directly.")
28
+ else:
29
+ if not context.workspace:
30
+ error_msg = f"Relative path '{path}' provided, but no workspace is configured for agent '{context.agent_id}'. A workspace is required to resolve relative paths."
31
+ logger.error(error_msg)
32
+ raise ValueError(error_msg)
33
+
34
+ base_path = context.workspace.get_base_path()
35
+ if not base_path or not isinstance(base_path, str):
36
+ error_msg = f"Agent '{context.agent_id}' has a configured workspace, but it provided an invalid base path ('{base_path}'). Cannot resolve relative path '{path}'."
37
+ logger.error(error_msg)
38
+ raise ValueError(error_msg)
39
+
40
+ final_path = os.path.join(base_path, path)
41
+ logger.debug(f"Path '{path}' is relative. Resolved to '{final_path}' using workspace base path '{base_path}'.")
42
+
43
+ # It's good practice to normalize the path to handle things like '..'
44
+ final_path = os.path.normpath(final_path)
45
+
46
+ if not os.path.exists(final_path):
47
+ raise FileNotFoundError(f"The file at resolved path {final_path} does not exist.")
48
+
23
49
  try:
24
- with open(path, 'r', encoding='utf-8') as file:
50
+ with open(final_path, 'r', encoding='utf-8') as file:
25
51
  content = file.read()
52
+ logger.info(f"File successfully read from '{final_path}' for agent '{context.agent_id}'.")
26
53
  return content
27
54
  except Exception as e:
28
- logger.error(f"Error reading file {path} for agent {context.agent_id}: {e}", exc_info=True)
29
- raise IOError(f"Could not read file at {path}: {str(e)}")
55
+ logger.error(f"Error reading file from final path '{final_path}' for agent {context.agent_id}: {e}", exc_info=True)
56
+ raise IOError(f"Could not read file at {final_path}: {str(e)}")
@@ -3,29 +3,57 @@ import logging
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from autobyteus.tools import tool
6
+ from autobyteus.tools.tool_category import ToolCategory
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from autobyteus.agent.context import AgentContext
9
10
 
10
11
  logger = logging.getLogger(__name__)
11
12
 
12
- @tool(name="FileWriter")
13
+ @tool(name="FileWriter", category=ToolCategory.FILE_SYSTEM)
13
14
  async def file_writer(context: 'AgentContext', path: str, content: str) -> str:
14
15
  """
15
16
  Creates or overwrites a file with specified content.
16
- 'path' is the path where the file will be written.
17
+ 'path' is the path where the file will be written. If relative, it must be resolved against a configured agent workspace.
17
18
  'content' is the string content to write.
18
19
  Creates parent directories if they don't exist.
20
+ Raises ValueError if a relative path is given without a valid workspace.
19
21
  Raises IOError if file writing fails.
20
22
  """
21
- logger.debug(f"Functional FileWriter tool for agent {context.agent_id}, path: {path}")
23
+ logger.debug(f"Functional FileWriter tool for agent {context.agent_id}, initial path: {path}")
24
+
25
+ final_path: str
26
+ if os.path.isabs(path):
27
+ final_path = path
28
+ logger.debug(f"Path '{path}' is absolute. Using it directly.")
29
+ else:
30
+ if not context.workspace:
31
+ error_msg = f"Relative path '{path}' provided, but no workspace is configured for agent '{context.agent_id}'. A workspace is required to resolve relative paths."
32
+ logger.error(error_msg)
33
+ raise ValueError(error_msg)
34
+
35
+ base_path = context.workspace.get_base_path()
36
+ if not base_path or not isinstance(base_path, str):
37
+ error_msg = f"Agent '{context.agent_id}' has a configured workspace, but it provided an invalid base path ('{base_path}'). Cannot resolve relative path '{path}'."
38
+ logger.error(error_msg)
39
+ raise ValueError(error_msg)
40
+
41
+ final_path = os.path.join(base_path, path)
42
+ logger.debug(f"Path '{path}' is relative. Resolved to '{final_path}' using workspace base path '{base_path}'.")
43
+
22
44
  try:
23
- dir_path = os.path.dirname(path)
24
- if dir_path: # Only if path includes a directory part
45
+ # It's good practice to normalize the path to handle things like '..'
46
+ final_path = os.path.normpath(final_path)
47
+
48
+ dir_path = os.path.dirname(final_path)
49
+ if dir_path:
25
50
  os.makedirs(dir_path, exist_ok=True)
26
- with open(path, 'w', encoding='utf-8') as file:
51
+
52
+ with open(final_path, 'w', encoding='utf-8') as file:
27
53
  file.write(content)
28
- return f"File created/updated at {path}"
54
+
55
+ logger.info(f"File successfully written to '{final_path}' for agent '{context.agent_id}'.")
56
+ return f"File created/updated at {final_path}"
29
57
  except Exception as e:
30
- logger.error(f"Error writing file {path} for agent {context.agent_id}: {e}", exc_info=True)
31
- raise IOError(f"Could not write file at {path}: {str(e)}")
58
+ logger.error(f"Error writing file to final path '{final_path}' for agent {context.agent_id}: {e}", exc_info=True)
59
+ raise IOError(f"Could not write file at '{final_path}': {str(e)}")
@@ -8,6 +8,7 @@ from autobyteus.tools.base_tool import BaseTool
8
8
  from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
9
9
  from autobyteus.tools.tool_config import ToolConfig
10
10
  from autobyteus.tools.registry import default_tool_registry, ToolDefinition
11
+ from autobyteus.tools.tool_origin import ToolOrigin
11
12
  from autobyteus.tools.tool_category import ToolCategory
12
13
 
13
14
  if TYPE_CHECKING:
@@ -200,7 +201,8 @@ def tool(
200
201
  name: Optional[str] = None,
201
202
  description: Optional[str] = None,
202
203
  argument_schema: Optional[ParameterSchema] = None,
203
- config_schema: Optional[ParameterSchema] = None
204
+ config_schema: Optional[ParameterSchema] = None,
205
+ category: str = ToolCategory.GENERAL
204
206
  ):
205
207
  def decorator(func: Callable) -> FunctionalTool:
206
208
  tool_name = name or func.__name__
@@ -227,8 +229,6 @@ def tool(
227
229
  instantiation_config=inst_config.params if inst_config else None
228
230
  )
229
231
 
230
- # The decorator's responsibility is now just to assemble the raw metadata
231
- # and create the definition. It does NOT generate usage strings.
232
232
  tool_def = ToolDefinition(
233
233
  name=tool_name,
234
234
  description=tool_desc,
@@ -236,7 +236,8 @@ def tool(
236
236
  config_schema=config_schema,
237
237
  custom_factory=factory,
238
238
  tool_class=None,
239
- category=ToolCategory.LOCAL
239
+ origin=ToolOrigin.LOCAL,
240
+ category=category
240
241
  )
241
242
  default_tool_registry.register_tool(tool_def)
242
243
 
@@ -7,6 +7,7 @@ from typing import Optional, TYPE_CHECKING, Any
7
7
  from autobyteus.tools.base_tool import BaseTool
8
8
  from autobyteus.tools.tool_config import ToolConfig
9
9
  from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
+ from autobyteus.tools.tool_category import ToolCategory
10
11
  from PIL import Image
11
12
  from io import BytesIO
12
13
  from autobyteus.utils.file_utils import get_default_download_folder
@@ -18,6 +19,7 @@ if TYPE_CHECKING:
18
19
  logger = logging.getLogger(__name__)
19
20
 
20
21
  class ImageDownloader(BaseTool):
22
+ CATEGORY = ToolCategory.WEB
21
23
  supported_formats = ['.jpeg', '.jpg', '.gif', '.png', '.webp']
22
24
 
23
25
  def __init__(self, config: Optional[ToolConfig] = None):