autobyteus 1.1.8__py3-none-any.whl → 1.2.0__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 (127) hide show
  1. autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +6 -2
  2. autobyteus/agent/handlers/inter_agent_message_event_handler.py +17 -19
  3. autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +6 -3
  4. autobyteus/agent/handlers/tool_result_event_handler.py +61 -18
  5. autobyteus/agent/handlers/user_input_message_event_handler.py +19 -10
  6. autobyteus/agent/hooks/base_phase_hook.py +17 -0
  7. autobyteus/agent/hooks/hook_registry.py +15 -27
  8. autobyteus/agent/input_processor/base_user_input_processor.py +17 -1
  9. autobyteus/agent/input_processor/processor_registry.py +15 -27
  10. autobyteus/agent/llm_response_processor/base_processor.py +17 -1
  11. autobyteus/agent/llm_response_processor/processor_registry.py +15 -24
  12. autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +14 -0
  13. autobyteus/agent/message/agent_input_user_message.py +15 -2
  14. autobyteus/agent/message/send_message_to.py +1 -1
  15. autobyteus/agent/processor_option.py +17 -0
  16. autobyteus/agent/sender_type.py +1 -0
  17. autobyteus/agent/system_prompt_processor/base_processor.py +17 -1
  18. autobyteus/agent/system_prompt_processor/processor_registry.py +15 -27
  19. autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +10 -0
  20. autobyteus/agent/tool_execution_result_processor/base_processor.py +17 -1
  21. autobyteus/agent/tool_execution_result_processor/processor_registry.py +15 -1
  22. autobyteus/agent/workspace/base_workspace.py +1 -1
  23. autobyteus/agent/workspace/workspace_definition.py +1 -1
  24. autobyteus/agent_team/bootstrap_steps/team_context_initialization_step.py +1 -1
  25. autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py +2 -2
  26. autobyteus/agent_team/task_notification/__init__.py +4 -0
  27. autobyteus/agent_team/task_notification/activation_policy.py +70 -0
  28. autobyteus/agent_team/task_notification/system_event_driven_agent_task_notifier.py +56 -122
  29. autobyteus/agent_team/task_notification/task_activator.py +66 -0
  30. autobyteus/cli/agent_team_tui/state.py +17 -20
  31. autobyteus/cli/agent_team_tui/widgets/focus_pane.py +1 -1
  32. autobyteus/cli/agent_team_tui/widgets/task_board_panel.py +1 -1
  33. autobyteus/clients/__init__.py +10 -0
  34. autobyteus/clients/autobyteus_client.py +318 -0
  35. autobyteus/clients/cert_utils.py +105 -0
  36. autobyteus/clients/certificates/cert.pem +34 -0
  37. autobyteus/events/event_types.py +2 -2
  38. autobyteus/llm/api/autobyteus_llm.py +1 -1
  39. autobyteus/llm/api/gemini_llm.py +45 -54
  40. autobyteus/llm/api/qwen_llm.py +25 -0
  41. autobyteus/llm/api/zhipu_llm.py +26 -0
  42. autobyteus/llm/autobyteus_provider.py +9 -3
  43. autobyteus/llm/llm_factory.py +39 -0
  44. autobyteus/llm/ollama_provider_resolver.py +1 -0
  45. autobyteus/llm/providers.py +1 -0
  46. autobyteus/llm/token_counter/token_counter_factory.py +3 -0
  47. autobyteus/llm/token_counter/zhipu_token_counter.py +24 -0
  48. autobyteus/multimedia/audio/api/autobyteus_audio_client.py +5 -2
  49. autobyteus/multimedia/audio/api/gemini_audio_client.py +84 -153
  50. autobyteus/multimedia/audio/audio_client_factory.py +47 -22
  51. autobyteus/multimedia/audio/audio_model.py +13 -6
  52. autobyteus/multimedia/audio/autobyteus_audio_provider.py +9 -3
  53. autobyteus/multimedia/audio/base_audio_client.py +3 -1
  54. autobyteus/multimedia/image/api/autobyteus_image_client.py +13 -6
  55. autobyteus/multimedia/image/api/gemini_image_client.py +72 -130
  56. autobyteus/multimedia/image/api/openai_image_client.py +4 -2
  57. autobyteus/multimedia/image/autobyteus_image_provider.py +9 -3
  58. autobyteus/multimedia/image/base_image_client.py +6 -2
  59. autobyteus/multimedia/image/image_client_factory.py +20 -19
  60. autobyteus/multimedia/image/image_model.py +13 -6
  61. autobyteus/multimedia/providers.py +1 -0
  62. autobyteus/task_management/__init__.py +10 -10
  63. autobyteus/task_management/base_task_board.py +14 -6
  64. autobyteus/task_management/converters/__init__.py +0 -2
  65. autobyteus/task_management/converters/task_board_converter.py +7 -16
  66. autobyteus/task_management/events.py +6 -6
  67. autobyteus/task_management/in_memory_task_board.py +48 -38
  68. autobyteus/task_management/schemas/__init__.py +2 -2
  69. autobyteus/task_management/schemas/{plan_definition.py → task_definition.py} +6 -7
  70. autobyteus/task_management/schemas/task_status_report.py +1 -2
  71. autobyteus/task_management/task.py +60 -0
  72. autobyteus/task_management/tools/__init__.py +6 -2
  73. autobyteus/task_management/tools/assign_task_to.py +125 -0
  74. autobyteus/task_management/tools/get_my_tasks.py +80 -0
  75. autobyteus/task_management/tools/get_task_board_status.py +3 -3
  76. autobyteus/task_management/tools/publish_task.py +77 -0
  77. autobyteus/task_management/tools/publish_tasks.py +74 -0
  78. autobyteus/task_management/tools/update_task_status.py +5 -5
  79. autobyteus/tools/__init__.py +54 -16
  80. autobyteus/tools/base_tool.py +4 -4
  81. autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +1 -1
  82. autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +1 -1
  83. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +1 -1
  84. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +1 -1
  85. autobyteus/tools/browser/standalone/navigate_to.py +1 -1
  86. autobyteus/tools/browser/standalone/web_page_pdf_generator.py +1 -1
  87. autobyteus/tools/browser/standalone/webpage_image_downloader.py +1 -1
  88. autobyteus/tools/browser/standalone/webpage_reader.py +1 -1
  89. autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +1 -1
  90. autobyteus/tools/download_media_tool.py +136 -0
  91. autobyteus/tools/file/file_editor.py +200 -0
  92. autobyteus/tools/functional_tool.py +1 -1
  93. autobyteus/tools/google_search.py +1 -1
  94. autobyteus/tools/mcp/factory.py +1 -1
  95. autobyteus/tools/mcp/schema_mapper.py +1 -1
  96. autobyteus/tools/mcp/tool.py +1 -1
  97. autobyteus/tools/multimedia/__init__.py +2 -0
  98. autobyteus/tools/multimedia/audio_tools.py +10 -20
  99. autobyteus/tools/multimedia/image_tools.py +21 -22
  100. autobyteus/tools/multimedia/media_reader_tool.py +117 -0
  101. autobyteus/tools/pydantic_schema_converter.py +1 -1
  102. autobyteus/tools/registry/tool_definition.py +1 -1
  103. autobyteus/tools/timer.py +1 -1
  104. autobyteus/tools/tool_meta.py +1 -1
  105. autobyteus/tools/usage/formatters/default_json_example_formatter.py +1 -1
  106. autobyteus/tools/usage/formatters/default_xml_example_formatter.py +1 -1
  107. autobyteus/tools/usage/formatters/default_xml_schema_formatter.py +59 -3
  108. autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +1 -1
  109. autobyteus/tools/usage/formatters/google_json_example_formatter.py +1 -1
  110. autobyteus/tools/usage/formatters/openai_json_example_formatter.py +1 -1
  111. autobyteus/tools/usage/parsers/_string_decoders.py +18 -0
  112. autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +9 -1
  113. autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +15 -1
  114. autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +4 -1
  115. autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +4 -1
  116. autobyteus/{tools → utils}/parameter_schema.py +1 -1
  117. {autobyteus-1.1.8.dist-info → autobyteus-1.2.0.dist-info}/METADATA +4 -3
  118. {autobyteus-1.1.8.dist-info → autobyteus-1.2.0.dist-info}/RECORD +122 -108
  119. examples/run_poem_writer.py +1 -1
  120. autobyteus/task_management/converters/task_plan_converter.py +0 -48
  121. autobyteus/task_management/task_plan.py +0 -110
  122. autobyteus/task_management/tools/publish_task_plan.py +0 -101
  123. autobyteus/tools/image_downloader.py +0 -99
  124. autobyteus/tools/pdf_downloader.py +0 -89
  125. {autobyteus-1.1.8.dist-info → autobyteus-1.2.0.dist-info}/WHEEL +0 -0
  126. {autobyteus-1.1.8.dist-info → autobyteus-1.2.0.dist-info}/licenses/LICENSE +0 -0
  127. {autobyteus-1.1.8.dist-info → autobyteus-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,74 @@
1
+ # file: autobyteus/autobyteus/task_management/tools/publish_tasks.py
2
+ import logging
3
+ from typing import TYPE_CHECKING, Optional, Dict, Any
4
+
5
+ from pydantic import ValidationError
6
+
7
+ from autobyteus.tools.base_tool import BaseTool
8
+ from autobyteus.tools.tool_category import ToolCategory
9
+ from autobyteus.utils.parameter_schema import ParameterSchema
10
+ from autobyteus.tools.pydantic_schema_converter import pydantic_to_parameter_schema
11
+ from autobyteus.task_management.schemas import TasksDefinitionSchema
12
+ from autobyteus.task_management.task import Task
13
+
14
+ if TYPE_CHECKING:
15
+ from autobyteus.agent.context import AgentContext
16
+ from autobyteus.agent_team.context import AgentTeamContext
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ class PublishTasks(BaseTool):
21
+ """
22
+ A tool to publish multiple tasks to the task board. This is an additive-only operation.
23
+ """
24
+
25
+ CATEGORY = ToolCategory.TASK_MANAGEMENT
26
+
27
+ @classmethod
28
+ def get_name(cls) -> str:
29
+ return "PublishTasks"
30
+
31
+ @classmethod
32
+ def get_description(cls) -> str:
33
+ return (
34
+ "Adds a list of new tasks to the team's shared task board. This action is additive and "
35
+ "does not affect existing tasks or the team's overall goal."
36
+ )
37
+
38
+ @classmethod
39
+ def get_argument_schema(cls) -> Optional[ParameterSchema]:
40
+ return pydantic_to_parameter_schema(TasksDefinitionSchema)
41
+
42
+ async def _execute(self, context: 'AgentContext', **kwargs: Any) -> str:
43
+ agent_name = context.config.name
44
+ logger.info(f"Agent '{agent_name}' is executing PublishTasks.")
45
+
46
+ team_context: Optional['AgentTeamContext'] = context.custom_data.get("team_context")
47
+ if not team_context:
48
+ error_msg = "Error: Team context is not available. Cannot access the task board."
49
+ logger.error(f"Agent '{agent_name}': {error_msg}")
50
+ return error_msg
51
+
52
+ task_board = getattr(team_context.state, 'task_board', None)
53
+ if not task_board:
54
+ error_msg = "Error: Task board has not been initialized for this team."
55
+ logger.error(f"Agent '{agent_name}': {error_msg}")
56
+ return error_msg
57
+
58
+ try:
59
+ tasks_def_schema = TasksDefinitionSchema(**kwargs)
60
+ final_tasks = [Task(**task_def.model_dump()) for task_def in tasks_def_schema.tasks]
61
+ except (ValidationError, ValueError) as e:
62
+ error_msg = f"Invalid task definitions provided: {e}"
63
+ logger.warning(f"Agent '{agent_name}' provided an invalid definition for PublishTasks: {error_msg}")
64
+ return f"Error: {error_msg}"
65
+
66
+ if task_board.add_tasks(tasks=final_tasks):
67
+ success_msg = f"Successfully published {len(final_tasks)} new task(s) to the task board."
68
+ logger.info(f"Agent '{agent_name}': {success_msg}")
69
+ return success_msg
70
+ else:
71
+ # This path is less likely now but kept for robustness.
72
+ error_msg = "Failed to publish tasks to the board for an unknown reason."
73
+ logger.error(f"Agent '{agent_name}': {error_msg}")
74
+ return f"Error: {error_msg}"
@@ -5,7 +5,7 @@ from pydantic import ValidationError
5
5
 
6
6
  from autobyteus.tools.base_tool import BaseTool
7
7
  from autobyteus.tools.tool_category import ToolCategory
8
- from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
8
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
9
9
  from autobyteus.tools.pydantic_schema_converter import pydantic_to_parameter_schema
10
10
  from autobyteus.task_management.base_task_board import TaskStatus
11
11
  from autobyteus.task_management.deliverable import FileDeliverable
@@ -77,12 +77,12 @@ class UpdateTaskStatus(BaseTool):
77
77
  logger.error(f"Agent '{agent_name}': {error_msg}")
78
78
  return error_msg
79
79
 
80
- if not task_board.current_plan:
81
- error_msg = "Error: No task plan is currently loaded on the task board."
82
- logger.warning(f"Agent '{agent_name}' tried to update task status, but no plan is loaded.")
80
+ if not task_board.tasks:
81
+ error_msg = "Error: No tasks are currently loaded on the task board."
82
+ logger.warning(f"Agent '{agent_name}' tried to update task status, but the board is empty.")
83
83
  return error_msg
84
84
 
85
- target_task = next((t for t in task_board.current_plan.tasks if t.task_name == task_name), None)
85
+ target_task = next((t for t in task_board.tasks if t.task_name == task_name), None)
86
86
 
87
87
  if not target_task:
88
88
  error_msg = f"Failed to update status for task '{task_name}'. The task name does not exist on the current plan."
@@ -5,40 +5,77 @@ for creating tools within the AutoByteUs framework.
5
5
  It also contains implementations of various standard tools.
6
6
  """
7
7
 
8
+ import logging
9
+
8
10
  # Core components for defining tools
9
11
  from .base_tool import BaseTool
10
12
  from .functional_tool import tool # The @tool decorator
11
- from .parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
13
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
12
14
  from .tool_config import ToolConfig # Configuration data object, primarily for class-based tools
13
15
  from .tool_origin import ToolOrigin
14
16
  from .tool_category import ToolCategory
15
17
 
18
+ logger = logging.getLogger(__name__)
19
+
16
20
  # --- Re-export specific tools for easier access ---
17
21
 
18
22
  # Functional tools (decorated functions are now instances)
19
- from .pdf_downloader import pdf_downloader
20
23
  from .bash.bash_executor import bash_executor
21
24
  from .file.file_reader import file_reader
22
25
  from .file.file_writer import file_writer
26
+ from .file.file_editor import file_edit
23
27
 
24
28
  # General Class-based tools
25
- from .google_search import GoogleSearch
26
- from .image_downloader import ImageDownloader
29
+ try:
30
+ from .google_search import GoogleSearch
31
+ except ModuleNotFoundError as import_err:
32
+ logger.warning("GoogleSearch tool not available: %s", import_err)
33
+ GoogleSearch = None
27
34
  from .timer import Timer
28
- from .multimedia.image_tools import GenerateImageTool, EditImageTool
35
+ try:
36
+ from .multimedia.image_tools import GenerateImageTool, EditImageTool
37
+ except ModuleNotFoundError as import_err:
38
+ logger.warning("Image tools not available: %s", import_err)
39
+ GenerateImageTool = None
40
+ EditImageTool = None
41
+ try:
42
+ from .multimedia.media_reader_tool import ReadMediaFile
43
+ except ModuleNotFoundError as import_err:
44
+ logger.warning("Media reader tool not available: %s", import_err)
45
+ ReadMediaFile = None
46
+ try:
47
+ from .download_media_tool import DownloadMediaTool
48
+ except ModuleNotFoundError as import_err:
49
+ logger.warning("Download media tool not available: %s", import_err)
50
+ DownloadMediaTool = None
29
51
 
30
52
  # Standalone Browser tools
31
- from .browser.standalone.navigate_to import NavigateTo as StandaloneNavigateTo # Alias to avoid name clash
32
- from .browser.standalone.webpage_reader import WebPageReader as StandaloneWebPageReader # Alias
33
- from .browser.standalone.webpage_screenshot_taker import WebPageScreenshotTaker as StandaloneWebPageScreenshotTaker # Alias
34
- from .browser.standalone.webpage_image_downloader import WebPageImageDownloader
35
- from .browser.standalone.web_page_pdf_generator import WebPagePDFGenerator
53
+ try:
54
+ from .browser.standalone.navigate_to import NavigateTo as StandaloneNavigateTo # Alias to avoid name clash
55
+ from .browser.standalone.webpage_reader import WebPageReader as StandaloneWebPageReader # Alias
56
+ from .browser.standalone.webpage_screenshot_taker import WebPageScreenshotTaker as StandaloneWebPageScreenshotTaker # Alias
57
+ from .browser.standalone.webpage_image_downloader import WebPageImageDownloader
58
+ from .browser.standalone.web_page_pdf_generator import WebPagePDFGenerator
59
+ except ModuleNotFoundError as import_err:
60
+ logger.warning('Standalone browser tools not available: %s', import_err)
61
+ StandaloneNavigateTo = None
62
+ StandaloneWebPageReader = None
63
+ StandaloneWebPageScreenshotTaker = None
64
+ WebPageImageDownloader = None
65
+ WebPagePDFGenerator = None
36
66
 
37
67
  # Session-Aware Browser tools
38
- from .browser.session_aware.browser_session_aware_navigate_to import BrowserSessionAwareNavigateTo
39
- from .browser.session_aware.browser_session_aware_web_element_trigger import BrowserSessionAwareWebElementTrigger
40
- from .browser.session_aware.browser_session_aware_webpage_reader import BrowserSessionAwareWebPageReader
41
- from .browser.session_aware.browser_session_aware_webpage_screenshot_taker import BrowserSessionAwareWebPageScreenshotTaker
68
+ try:
69
+ from .browser.session_aware.browser_session_aware_navigate_to import BrowserSessionAwareNavigateTo
70
+ from .browser.session_aware.browser_session_aware_web_element_trigger import BrowserSessionAwareWebElementTrigger
71
+ from .browser.session_aware.browser_session_aware_webpage_reader import BrowserSessionAwareWebPageReader
72
+ from .browser.session_aware.browser_session_aware_webpage_screenshot_taker import BrowserSessionAwareWebPageScreenshotTaker
73
+ except ModuleNotFoundError as import_err:
74
+ logger.warning('Session-aware browser tools not available: %s', import_err)
75
+ BrowserSessionAwareNavigateTo = None
76
+ BrowserSessionAwareWebElementTrigger = None
77
+ BrowserSessionAwareWebPageReader = None
78
+ BrowserSessionAwareWebPageScreenshotTaker = None
42
79
 
43
80
 
44
81
  __all__ = [
@@ -53,17 +90,18 @@ __all__ = [
53
90
  "ToolCategory",
54
91
 
55
92
  # Re-exported functional tool instances
56
- "pdf_downloader",
57
93
  "bash_executor",
58
94
  "file_reader",
59
95
  "file_writer",
96
+ "file_edit",
60
97
 
61
98
  # Re-exported general class-based tools
62
99
  "GoogleSearch",
63
- "ImageDownloader",
64
100
  "Timer",
65
101
  "GenerateImageTool",
66
102
  "EditImageTool",
103
+ "ReadMediaFile",
104
+ "DownloadMediaTool",
67
105
 
68
106
  # Re-exported Standalone Browser tools
69
107
  "StandaloneNavigateTo",
@@ -5,14 +5,14 @@ from abc import ABC, abstractmethod
5
5
  from typing import Optional, Any, TYPE_CHECKING, List as TypingList, Dict, Union
6
6
 
7
7
  from autobyteus.events.event_emitter import EventEmitter
8
- from autobyteus.tools.parameter_schema import ParameterType
8
+ from autobyteus.utils.parameter_schema import ParameterType
9
9
 
10
10
  from .tool_meta import ToolMeta
11
11
  from .tool_state import ToolState
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from autobyteus.agent.context import AgentContext
15
- from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition
15
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition
16
16
  from autobyteus.tools.tool_config import ToolConfig
17
17
  from .tool_state import ToolState
18
18
  from autobyteus.tools.registry import ToolDefinition
@@ -90,8 +90,8 @@ class BaseTool(ABC, EventEmitter, metaclass=ToolMeta):
90
90
  if item_schema_dict and isinstance(item_schema_dict, dict) and item_schema_dict.get("type") == "object":
91
91
  # Create a temporary ParameterSchema for the item type to enable recursion.
92
92
  # This is a simplified conversion for coercion purposes only.
93
- from .parameter_schema import ParameterSchema as TempSchema
94
- from .parameter_schema import ParameterDefinition as TempDef
93
+ from autobyteus.utils.parameter_schema import ParameterSchema as TempSchema
94
+ from autobyteus.utils.parameter_schema import ParameterDefinition as TempDef
95
95
 
96
96
  item_param_schema = TempSchema()
97
97
  props = item_schema_dict.get("properties", {})
@@ -6,7 +6,7 @@ from urllib.parse import urlparse
6
6
  from typing import Optional, TYPE_CHECKING, Any
7
7
  import logging
8
8
 
9
- from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
9
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from autobyteus.agent.context import AgentContext
@@ -7,7 +7,7 @@ import logging
7
7
  from autobyteus.tools.browser.session_aware.browser_session_aware_tool import BrowserSessionAwareTool
8
8
  from autobyteus.tools.browser.session_aware.shared_browser_session import SharedBrowserSession
9
9
  from autobyteus.tools.browser.session_aware.web_element_action import WebElementAction
10
- from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
11
11
  from autobyteus.tools.tool_config import ToolConfig
12
12
  from autobyteus.tools.tool_category import ToolCategory
13
13
 
@@ -5,7 +5,7 @@ from typing import Optional, TYPE_CHECKING, Any
5
5
  from autobyteus.tools.browser.session_aware.browser_session_aware_tool import BrowserSessionAwareTool
6
6
  from autobyteus.tools.browser.session_aware.shared_browser_session import SharedBrowserSession
7
7
  from autobyteus.tools.tool_config import ToolConfig
8
- from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
8
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
9
9
  from autobyteus.tools.tool_category import ToolCategory
10
10
  from autobyteus.utils.html_cleaner import clean, CleaningMode
11
11
 
@@ -6,7 +6,7 @@ from typing import Optional, TYPE_CHECKING, Any
6
6
  from autobyteus.tools.browser.session_aware.browser_session_aware_tool import BrowserSessionAwareTool
7
7
  from autobyteus.tools.browser.session_aware.shared_browser_session import SharedBrowserSession
8
8
  from autobyteus.tools.tool_config import ToolConfig
9
- from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
9
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
10
  from autobyteus.tools.tool_category import ToolCategory
11
11
 
12
12
  if TYPE_CHECKING:
@@ -6,7 +6,7 @@ from urllib.parse import urlparse
6
6
  from typing import Optional, TYPE_CHECKING, Any
7
7
  import logging
8
8
 
9
- from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
9
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from autobyteus.agent.context import AgentContext
@@ -7,7 +7,7 @@ import logging
7
7
  from typing import Optional, TYPE_CHECKING, Any
8
8
  from urllib.parse import urlparse
9
9
 
10
- from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from autobyteus.agent.context import AgentContext
@@ -7,7 +7,7 @@ import logging
7
7
  from urllib.parse import urljoin, urlparse
8
8
  from typing import Optional, TYPE_CHECKING, Any, List
9
9
 
10
- from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from autobyteus.agent.context import AgentContext
@@ -7,7 +7,7 @@ import logging
7
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
- from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
11
11
  from autobyteus.tools.tool_category import ToolCategory
12
12
  from brui_core.ui_integrator import UIIntegrator
13
13
  from autobyteus.utils.html_cleaner import clean, CleaningMode
@@ -1,7 +1,7 @@
1
1
  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
- from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
4
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
5
5
  from autobyteus.tools.tool_category import ToolCategory
6
6
  from brui_core.ui_integrator import UIIntegrator
7
7
  import logging
@@ -0,0 +1,136 @@
1
+ import os
2
+ import logging
3
+ import mimetypes
4
+ import aiohttp
5
+ from typing import Optional, TYPE_CHECKING
6
+ from urllib.parse import urlparse
7
+
8
+ from autobyteus.tools.base_tool import BaseTool
9
+ from autobyteus.tools.tool_category import ToolCategory
10
+ from autobyteus.utils.file_utils import get_default_download_folder
11
+ from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
12
+
13
+ if TYPE_CHECKING:
14
+ from autobyteus.agent.context import AgentContext
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ class DownloadMediaTool(BaseTool):
19
+ """
20
+ A unified tool to download any media file (e.g., image, PDF, audio) from a URL.
21
+ """
22
+ CATEGORY = ToolCategory.WEB
23
+
24
+ @classmethod
25
+ def get_name(cls) -> str:
26
+ return "DownloadMedia"
27
+
28
+ @classmethod
29
+ def get_description(cls) -> str:
30
+ return (
31
+ "Downloads various media files (e.g., images like PNG/JPG, documents like PDF, audio like MP3/WAV) "
32
+ "from a direct URL and saves them locally. It intelligently determines the correct file extension "
33
+ "based on the content type. Returns the absolute path to the downloaded file."
34
+ )
35
+
36
+ @classmethod
37
+ def get_argument_schema(cls) -> ParameterSchema:
38
+ schema = ParameterSchema()
39
+ schema.add_parameter(ParameterDefinition(
40
+ name="url",
41
+ param_type=ParameterType.STRING,
42
+ description="The direct URL of the media file to download.",
43
+ required=True
44
+ ))
45
+ schema.add_parameter(ParameterDefinition(
46
+ name="filename",
47
+ param_type=ParameterType.STRING,
48
+ description="The desired base name for the downloaded file (e.g., 'vacation_photo', 'annual_report'). The tool will automatically add the correct file extension.",
49
+ required=True
50
+ ))
51
+ schema.add_parameter(ParameterDefinition(
52
+ name="folder",
53
+ param_type=ParameterType.STRING,
54
+ description="Optional. A custom directory path to save the file. If not provided, the system's default download folder will be used.",
55
+ required=False
56
+ ))
57
+ return schema
58
+
59
+ async def _execute(self, context: 'AgentContext', url: str, filename: str, folder: Optional[str] = None) -> str:
60
+ # 1. Determine download directory
61
+ try:
62
+ if folder:
63
+ # Security: prevent path traversal attacks.
64
+ if ".." in folder:
65
+ raise ValueError("Security error: 'folder' path cannot contain '..'.")
66
+ destination_dir = os.path.abspath(folder)
67
+ else:
68
+ destination_dir = get_default_download_folder()
69
+
70
+ os.makedirs(destination_dir, exist_ok=True)
71
+ except Exception as e:
72
+ logger.error(f"Error preparing download directory '{folder or 'default'}': {e}", exc_info=True)
73
+ raise IOError(f"Failed to create or access download directory: {e}")
74
+
75
+ # 2. Sanitize filename provided by the LLM
76
+ if not filename or ".." in filename or os.path.isabs(filename) or "/" in filename or "\\" in filename:
77
+ raise ValueError("Invalid filename. It must be a simple name without any path characters ('..', '/', '\\').")
78
+
79
+ logger.info(f"Attempting to download from {url} to save as '{filename}' in '{destination_dir}'.")
80
+
81
+ # 3. Download and process file asynchronously
82
+ try:
83
+ async with aiohttp.ClientSession() as session:
84
+ async with session.get(url, timeout=60) as response:
85
+ response.raise_for_status()
86
+
87
+ # 4. Intelligently determine file extension from Content-Type header
88
+ content_type = response.headers.get('Content-Type')
89
+ correct_ext = ''
90
+ if content_type:
91
+ mime_type = content_type.split(';')[0].strip()
92
+ guess = mimetypes.guess_extension(mime_type)
93
+ if guess:
94
+ correct_ext = guess
95
+ logger.debug(f"Determined extension '{correct_ext}' from Content-Type: '{mime_type}'")
96
+
97
+ # Fallback to URL extension if Content-Type is generic or missing
98
+ if not correct_ext or correct_ext == '.bin':
99
+ url_path = urlparse(url).path
100
+ _, ext_from_url = os.path.splitext(os.path.basename(url_path))
101
+ if ext_from_url and len(ext_from_url) > 1: # Ensure it's not just a dot
102
+ logger.debug(f"Using fallback extension '{ext_from_url}' from URL.")
103
+ correct_ext = ext_from_url
104
+
105
+ if not correct_ext:
106
+ logger.warning("Could not determine a file extension. The file will be saved without one.")
107
+
108
+ # 5. Construct final filename and path
109
+ base_filename, _ = os.path.splitext(filename)
110
+ final_filename = f"{base_filename}{correct_ext}"
111
+ save_path = os.path.join(destination_dir, final_filename)
112
+
113
+ # Ensure filename is unique to avoid overwriting
114
+ counter = 1
115
+ while os.path.exists(save_path):
116
+ final_filename = f"{base_filename}_{counter}{correct_ext}"
117
+ save_path = os.path.join(destination_dir, final_filename)
118
+ counter += 1
119
+
120
+ # 6. Stream file content to disk
121
+ with open(save_path, 'wb') as f:
122
+ async for chunk in response.content.iter_chunked(8192):
123
+ f.write(chunk)
124
+
125
+ logger.info(f"Successfully downloaded and saved file to: {save_path}")
126
+ return f"Successfully downloaded file to: {save_path}"
127
+
128
+ except aiohttp.ClientError as e:
129
+ logger.error(f"Network error while downloading from {url}: {e}", exc_info=True)
130
+ raise ConnectionError(f"Failed to download from {url}: {e}")
131
+ except IOError as e:
132
+ logger.error(f"Failed to write downloaded file to {destination_dir}: {e}", exc_info=True)
133
+ raise
134
+ except Exception as e:
135
+ logger.error(f"An unexpected error occurred during download from {url}: {e}", exc_info=True)
136
+ raise RuntimeError(f"An unexpected error occurred: {e}")