autobyteus 1.1.3__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.
- autobyteus/agent/agent.py +1 -1
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +4 -2
- autobyteus/agent/context/agent_config.py +36 -5
- autobyteus/agent/events/worker_event_dispatcher.py +1 -2
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +1 -1
- autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +2 -2
- autobyteus/agent/handlers/tool_result_event_handler.py +48 -20
- autobyteus/agent/handlers/user_input_message_event_handler.py +1 -1
- autobyteus/agent/input_processor/__init__.py +1 -7
- autobyteus/agent/message/context_file_type.py +6 -0
- autobyteus/agent/message/send_message_to.py +68 -99
- autobyteus/agent/phases/discover.py +2 -1
- autobyteus/agent/runtime/agent_worker.py +1 -0
- autobyteus/agent/tool_execution_result_processor/__init__.py +9 -0
- autobyteus/agent/tool_execution_result_processor/base_processor.py +46 -0
- autobyteus/agent/tool_execution_result_processor/processor_definition.py +36 -0
- autobyteus/agent/tool_execution_result_processor/processor_meta.py +36 -0
- autobyteus/agent/tool_execution_result_processor/processor_registry.py +70 -0
- autobyteus/agent/workspace/base_workspace.py +17 -2
- autobyteus/cli/__init__.py +1 -1
- autobyteus/cli/cli_display.py +1 -1
- autobyteus/cli/workflow_tui/__init__.py +4 -0
- autobyteus/cli/workflow_tui/app.py +210 -0
- autobyteus/cli/workflow_tui/state.py +189 -0
- autobyteus/cli/workflow_tui/widgets/__init__.py +6 -0
- autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +149 -0
- autobyteus/cli/workflow_tui/widgets/focus_pane.py +335 -0
- autobyteus/cli/workflow_tui/widgets/logo.py +27 -0
- autobyteus/cli/workflow_tui/widgets/renderables.py +70 -0
- autobyteus/cli/workflow_tui/widgets/shared.py +51 -0
- autobyteus/cli/workflow_tui/widgets/status_bar.py +14 -0
- autobyteus/events/event_types.py +3 -0
- autobyteus/llm/api/lmstudio_llm.py +37 -0
- autobyteus/llm/api/openai_compatible_llm.py +20 -3
- autobyteus/llm/llm_factory.py +2 -0
- autobyteus/llm/lmstudio_provider.py +89 -0
- autobyteus/llm/providers.py +1 -0
- autobyteus/llm/token_counter/token_counter_factory.py +2 -0
- autobyteus/tools/__init__.py +2 -0
- autobyteus/tools/ask_user_input.py +2 -1
- autobyteus/tools/bash/bash_executor.py +2 -1
- autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +2 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +3 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -0
- autobyteus/tools/browser/standalone/google_search_ui.py +2 -0
- autobyteus/tools/browser/standalone/navigate_to.py +2 -0
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +3 -0
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +3 -0
- autobyteus/tools/browser/standalone/webpage_reader.py +2 -0
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +3 -0
- autobyteus/tools/file/file_reader.py +36 -9
- autobyteus/tools/file/file_writer.py +37 -9
- autobyteus/tools/functional_tool.py +5 -4
- autobyteus/tools/image_downloader.py +2 -0
- autobyteus/tools/mcp/tool_registrar.py +3 -1
- autobyteus/tools/pdf_downloader.py +2 -1
- autobyteus/tools/registry/tool_definition.py +12 -8
- autobyteus/tools/registry/tool_registry.py +50 -2
- autobyteus/tools/timer.py +2 -0
- autobyteus/tools/tool_category.py +14 -4
- autobyteus/tools/tool_meta.py +6 -1
- autobyteus/tools/tool_origin.py +10 -0
- autobyteus/workflow/agentic_workflow.py +93 -0
- autobyteus/{agent/workflow → workflow}/base_agentic_workflow.py +19 -27
- autobyteus/workflow/bootstrap_steps/__init__.py +20 -0
- autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +34 -0
- autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +23 -0
- autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +41 -0
- autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +108 -0
- autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +50 -0
- autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +25 -0
- autobyteus/workflow/context/__init__.py +17 -0
- autobyteus/workflow/context/team_manager.py +147 -0
- autobyteus/workflow/context/workflow_config.py +30 -0
- autobyteus/workflow/context/workflow_context.py +61 -0
- autobyteus/workflow/context/workflow_node_config.py +76 -0
- autobyteus/workflow/context/workflow_runtime_state.py +53 -0
- autobyteus/workflow/events/__init__.py +29 -0
- autobyteus/workflow/events/workflow_event_dispatcher.py +39 -0
- autobyteus/workflow/events/workflow_events.py +53 -0
- autobyteus/workflow/events/workflow_input_event_queue_manager.py +21 -0
- autobyteus/workflow/exceptions.py +8 -0
- autobyteus/workflow/factory/__init__.py +9 -0
- autobyteus/workflow/factory/workflow_factory.py +99 -0
- autobyteus/workflow/handlers/__init__.py +19 -0
- autobyteus/workflow/handlers/base_workflow_event_handler.py +16 -0
- autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py +61 -0
- autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +27 -0
- autobyteus/workflow/handlers/process_user_message_event_handler.py +46 -0
- autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +39 -0
- autobyteus/workflow/handlers/workflow_event_handler_registry.py +23 -0
- autobyteus/workflow/phases/__init__.py +11 -0
- autobyteus/workflow/phases/workflow_operational_phase.py +19 -0
- autobyteus/workflow/phases/workflow_phase_manager.py +48 -0
- autobyteus/workflow/runtime/__init__.py +13 -0
- autobyteus/workflow/runtime/workflow_runtime.py +82 -0
- autobyteus/workflow/runtime/workflow_worker.py +117 -0
- autobyteus/workflow/shutdown_steps/__init__.py +17 -0
- autobyteus/workflow/shutdown_steps/agent_team_shutdown_step.py +42 -0
- autobyteus/workflow/shutdown_steps/base_workflow_shutdown_step.py +16 -0
- autobyteus/workflow/shutdown_steps/bridge_cleanup_step.py +28 -0
- autobyteus/workflow/shutdown_steps/sub_workflow_shutdown_step.py +41 -0
- autobyteus/workflow/shutdown_steps/workflow_shutdown_orchestrator.py +35 -0
- autobyteus/workflow/streaming/__init__.py +26 -0
- autobyteus/workflow/streaming/agent_event_bridge.py +48 -0
- autobyteus/workflow/streaming/agent_event_multiplexer.py +70 -0
- autobyteus/workflow/streaming/workflow_event_bridge.py +50 -0
- autobyteus/workflow/streaming/workflow_event_notifier.py +83 -0
- autobyteus/workflow/streaming/workflow_event_stream.py +33 -0
- autobyteus/workflow/streaming/workflow_stream_event_payloads.py +28 -0
- autobyteus/workflow/streaming/workflow_stream_events.py +45 -0
- autobyteus/workflow/utils/__init__.py +9 -0
- autobyteus/workflow/utils/wait_for_idle.py +46 -0
- autobyteus/workflow/workflow_builder.py +151 -0
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/METADATA +16 -14
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/RECORD +134 -65
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/top_level.txt +1 -0
- examples/__init__.py +1 -0
- examples/discover_phase_transitions.py +104 -0
- examples/run_browser_agent.py +260 -0
- examples/run_google_slides_agent.py +286 -0
- examples/run_mcp_browser_client.py +174 -0
- examples/run_mcp_google_slides_client.py +270 -0
- examples/run_mcp_list_tools.py +189 -0
- examples/run_poem_writer.py +274 -0
- examples/run_sqlite_agent.py +293 -0
- examples/workflow/__init__.py +1 -0
- examples/workflow/run_basic_research_workflow.py +189 -0
- examples/workflow/run_code_review_workflow.py +269 -0
- examples/workflow/run_debate_workflow.py +212 -0
- examples/workflow/run_workflow_with_tui.py +153 -0
- autobyteus/agent/context/agent_phase_manager.py +0 -264
- autobyteus/agent/context/phases.py +0 -49
- autobyteus/agent/group/__init__.py +0 -0
- autobyteus/agent/group/agent_group.py +0 -164
- autobyteus/agent/group/agent_group_context.py +0 -81
- autobyteus/agent/input_processor/content_prefixing_input_processor.py +0 -41
- autobyteus/agent/input_processor/metadata_appending_input_processor.py +0 -34
- autobyteus/agent/input_processor/passthrough_input_processor.py +0 -33
- autobyteus/agent/workflow/__init__.py +0 -11
- autobyteus/agent/workflow/agentic_workflow.py +0 -89
- autobyteus/tools/mcp/registrar.py +0 -202
- autobyteus/workflow/simple_task.py +0 -98
- autobyteus/workflow/task.py +0 -147
- autobyteus/workflow/workflow.py +0 -49
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.3.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
|
-
|
|
29
|
-
|
|
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}")
|
autobyteus/llm/llm_factory.py
CHANGED
|
@@ -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)}")
|
autobyteus/llm/providers.py
CHANGED
|
@@ -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:
|
autobyteus/tools/__init__.py
CHANGED
|
@@ -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")
|
|
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.
|
|
@@ -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")
|
|
13
|
-
async def file_reader(context: 'AgentContext', path: str) -> str:
|
|
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
|
|
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
|
-
|
|
22
|
-
|
|
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(
|
|
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 {
|
|
29
|
-
raise IOError(f"Could not read file at {
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
51
|
+
|
|
52
|
+
with open(final_path, 'w', encoding='utf-8') as file:
|
|
27
53
|
file.write(content)
|
|
28
|
-
|
|
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 {
|
|
31
|
-
raise IOError(f"Could not write file at {
|
|
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
|
-
|
|
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):
|
|
@@ -11,6 +11,7 @@ from .types import BaseMcpConfig
|
|
|
11
11
|
from .server import BaseManagedMcpServer
|
|
12
12
|
|
|
13
13
|
from autobyteus.tools.registry import ToolRegistry, ToolDefinition
|
|
14
|
+
from autobyteus.tools.tool_origin import ToolOrigin
|
|
14
15
|
from autobyteus.tools.tool_category import ToolCategory
|
|
15
16
|
from autobyteus.utils.singleton import SingletonMeta
|
|
16
17
|
from mcp import types as mcp_types
|
|
@@ -70,7 +71,8 @@ class McpToolRegistrar(metaclass=SingletonMeta):
|
|
|
70
71
|
name=registered_name,
|
|
71
72
|
description=actual_desc,
|
|
72
73
|
argument_schema=actual_arg_schema,
|
|
73
|
-
|
|
74
|
+
origin=ToolOrigin.MCP,
|
|
75
|
+
category=server_config.server_id, # Use server_id as the category
|
|
74
76
|
metadata={"mcp_server_id": server_config.server_id}, # Store origin in generic metadata
|
|
75
77
|
custom_factory=tool_factory.create_tool,
|
|
76
78
|
config_schema=None,
|
|
@@ -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,
|