autobyteus 1.2.0__py3-none-any.whl → 1.2.3__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 +15 -5
- autobyteus/agent/bootstrap_steps/__init__.py +1 -3
- autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +3 -59
- autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +1 -4
- autobyteus/agent/bootstrap_steps/mcp_server_prewarming_step.py +1 -3
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +16 -13
- autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +2 -4
- autobyteus/agent/context/agent_config.py +43 -20
- autobyteus/agent/context/agent_context.py +23 -18
- autobyteus/agent/context/agent_runtime_state.py +23 -19
- autobyteus/agent/events/__init__.py +16 -1
- autobyteus/agent/events/agent_events.py +43 -3
- autobyteus/agent/events/agent_input_event_queue_manager.py +79 -26
- autobyteus/agent/events/event_store.py +57 -0
- autobyteus/agent/events/notifiers.py +74 -60
- autobyteus/agent/events/worker_event_dispatcher.py +21 -64
- autobyteus/agent/factory/agent_factory.py +52 -0
- autobyteus/agent/handlers/__init__.py +2 -0
- autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +51 -34
- autobyteus/agent/handlers/bootstrap_event_handler.py +155 -0
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +10 -0
- autobyteus/agent/handlers/lifecycle_event_logger.py +19 -11
- autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +10 -15
- autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +188 -48
- autobyteus/agent/handlers/tool_execution_approval_event_handler.py +0 -10
- autobyteus/agent/handlers/tool_invocation_request_event_handler.py +53 -48
- autobyteus/agent/handlers/tool_result_event_handler.py +7 -8
- autobyteus/agent/handlers/user_input_message_event_handler.py +10 -3
- autobyteus/agent/input_processor/memory_ingest_input_processor.py +40 -0
- autobyteus/agent/lifecycle/__init__.py +12 -0
- autobyteus/agent/lifecycle/base_processor.py +109 -0
- autobyteus/agent/lifecycle/events.py +35 -0
- autobyteus/agent/lifecycle/processor_definition.py +36 -0
- autobyteus/agent/lifecycle/processor_registry.py +106 -0
- autobyteus/agent/llm_request_assembler.py +98 -0
- autobyteus/agent/llm_response_processor/__init__.py +1 -8
- autobyteus/agent/message/context_file_type.py +1 -1
- autobyteus/agent/message/send_message_to.py +5 -4
- autobyteus/agent/runtime/agent_runtime.py +29 -21
- autobyteus/agent/runtime/agent_worker.py +98 -19
- autobyteus/agent/shutdown_steps/__init__.py +2 -0
- autobyteus/agent/shutdown_steps/agent_shutdown_orchestrator.py +2 -0
- autobyteus/agent/shutdown_steps/tool_cleanup_step.py +58 -0
- autobyteus/agent/status/__init__.py +14 -0
- autobyteus/agent/status/manager.py +93 -0
- autobyteus/agent/status/status_deriver.py +96 -0
- autobyteus/agent/{phases/phase_enum.py → status/status_enum.py} +16 -16
- autobyteus/agent/status/status_update_utils.py +73 -0
- autobyteus/agent/streaming/__init__.py +52 -5
- autobyteus/agent/streaming/adapters/__init__.py +18 -0
- autobyteus/agent/streaming/adapters/invocation_adapter.py +184 -0
- autobyteus/agent/streaming/adapters/tool_call_parsing.py +163 -0
- autobyteus/agent/streaming/adapters/tool_syntax_registry.py +67 -0
- autobyteus/agent/streaming/agent_event_stream.py +3 -178
- autobyteus/agent/streaming/api_tool_call/__init__.py +16 -0
- autobyteus/agent/streaming/api_tool_call/file_content_streamer.py +56 -0
- autobyteus/agent/streaming/api_tool_call/json_string_field_extractor.py +175 -0
- autobyteus/agent/streaming/api_tool_call_streaming_response_handler.py +4 -0
- autobyteus/agent/streaming/events/__init__.py +6 -0
- autobyteus/agent/streaming/events/stream_event_payloads.py +284 -0
- autobyteus/agent/streaming/events/stream_events.py +141 -0
- autobyteus/agent/streaming/handlers/__init__.py +15 -0
- autobyteus/agent/streaming/handlers/api_tool_call_streaming_response_handler.py +303 -0
- autobyteus/agent/streaming/handlers/parsing_streaming_response_handler.py +107 -0
- autobyteus/agent/streaming/handlers/pass_through_streaming_response_handler.py +107 -0
- autobyteus/agent/streaming/handlers/streaming_handler_factory.py +177 -0
- autobyteus/agent/streaming/handlers/streaming_response_handler.py +58 -0
- autobyteus/agent/streaming/parser/__init__.py +61 -0
- autobyteus/agent/streaming/parser/event_emitter.py +181 -0
- autobyteus/agent/streaming/parser/events.py +4 -0
- autobyteus/agent/streaming/parser/invocation_adapter.py +4 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/__init__.py +19 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/base.py +32 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/default.py +34 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/gemini.py +31 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/openai.py +64 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/registry.py +75 -0
- autobyteus/agent/streaming/parser/parser_context.py +227 -0
- autobyteus/agent/streaming/parser/parser_factory.py +132 -0
- autobyteus/agent/streaming/parser/sentinel_format.py +7 -0
- autobyteus/agent/streaming/parser/state_factory.py +62 -0
- autobyteus/agent/streaming/parser/states/__init__.py +1 -0
- autobyteus/agent/streaming/parser/states/base_state.py +60 -0
- autobyteus/agent/streaming/parser/states/custom_xml_tag_run_bash_parsing_state.py +38 -0
- autobyteus/agent/streaming/parser/states/custom_xml_tag_write_file_parsing_state.py +55 -0
- autobyteus/agent/streaming/parser/states/delimited_content_state.py +146 -0
- autobyteus/agent/streaming/parser/states/json_initialization_state.py +144 -0
- autobyteus/agent/streaming/parser/states/json_tool_parsing_state.py +137 -0
- autobyteus/agent/streaming/parser/states/sentinel_content_state.py +30 -0
- autobyteus/agent/streaming/parser/states/sentinel_initialization_state.py +117 -0
- autobyteus/agent/streaming/parser/states/text_state.py +78 -0
- autobyteus/agent/streaming/parser/states/xml_patch_file_tool_parsing_state.py +328 -0
- autobyteus/agent/streaming/parser/states/xml_run_bash_tool_parsing_state.py +129 -0
- autobyteus/agent/streaming/parser/states/xml_tag_initialization_state.py +151 -0
- autobyteus/agent/streaming/parser/states/xml_tool_parsing_state.py +63 -0
- autobyteus/agent/streaming/parser/states/xml_write_file_tool_parsing_state.py +343 -0
- autobyteus/agent/streaming/parser/strategies/__init__.py +17 -0
- autobyteus/agent/streaming/parser/strategies/base.py +24 -0
- autobyteus/agent/streaming/parser/strategies/json_tool_strategy.py +26 -0
- autobyteus/agent/streaming/parser/strategies/registry.py +28 -0
- autobyteus/agent/streaming/parser/strategies/sentinel_strategy.py +23 -0
- autobyteus/agent/streaming/parser/strategies/xml_tag_strategy.py +21 -0
- autobyteus/agent/streaming/parser/stream_scanner.py +167 -0
- autobyteus/agent/streaming/parser/streaming_parser.py +212 -0
- autobyteus/agent/streaming/parser/tool_call_parsing.py +4 -0
- autobyteus/agent/streaming/parser/tool_constants.py +7 -0
- autobyteus/agent/streaming/parser/tool_syntax_registry.py +4 -0
- autobyteus/agent/streaming/parser/xml_tool_parsing_state_registry.py +55 -0
- autobyteus/agent/streaming/parsing_streaming_response_handler.py +4 -0
- autobyteus/agent/streaming/pass_through_streaming_response_handler.py +4 -0
- autobyteus/agent/streaming/queue_streamer.py +3 -57
- autobyteus/agent/streaming/segments/__init__.py +5 -0
- autobyteus/agent/streaming/segments/segment_events.py +81 -0
- autobyteus/agent/streaming/stream_event_payloads.py +2 -198
- autobyteus/agent/streaming/stream_events.py +3 -128
- autobyteus/agent/streaming/streaming_handler_factory.py +4 -0
- autobyteus/agent/streaming/streaming_response_handler.py +4 -0
- autobyteus/agent/streaming/streams/__init__.py +5 -0
- autobyteus/agent/streaming/streams/agent_event_stream.py +197 -0
- autobyteus/agent/streaming/utils/__init__.py +5 -0
- autobyteus/agent/streaming/utils/queue_streamer.py +59 -0
- autobyteus/agent/system_prompt_processor/__init__.py +2 -0
- autobyteus/agent/system_prompt_processor/available_skills_processor.py +96 -0
- autobyteus/agent/system_prompt_processor/base_processor.py +1 -1
- autobyteus/agent/system_prompt_processor/processor_meta.py +15 -2
- autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +39 -58
- autobyteus/agent/token_budget.py +56 -0
- autobyteus/agent/tool_execution_result_processor/memory_ingest_tool_result_processor.py +29 -0
- autobyteus/agent/tool_invocation.py +16 -40
- autobyteus/agent/tool_invocation_preprocessor/__init__.py +9 -0
- autobyteus/agent/tool_invocation_preprocessor/base_preprocessor.py +45 -0
- autobyteus/agent/tool_invocation_preprocessor/processor_definition.py +15 -0
- autobyteus/agent/tool_invocation_preprocessor/processor_meta.py +33 -0
- autobyteus/agent/tool_invocation_preprocessor/processor_registry.py +60 -0
- autobyteus/agent/utils/wait_for_idle.py +12 -14
- autobyteus/agent/workspace/base_workspace.py +6 -27
- autobyteus/agent_team/agent_team.py +3 -3
- autobyteus/agent_team/agent_team_builder.py +1 -41
- autobyteus/agent_team/bootstrap_steps/__init__.py +0 -4
- autobyteus/agent_team/bootstrap_steps/agent_configuration_preparation_step.py +8 -18
- autobyteus/agent_team/bootstrap_steps/agent_team_bootstrapper.py +4 -16
- autobyteus/agent_team/bootstrap_steps/base_agent_team_bootstrap_step.py +1 -2
- autobyteus/agent_team/bootstrap_steps/coordinator_initialization_step.py +1 -2
- autobyteus/agent_team/bootstrap_steps/task_notifier_initialization_step.py +5 -6
- autobyteus/agent_team/bootstrap_steps/team_context_initialization_step.py +15 -15
- autobyteus/agent_team/context/agent_team_config.py +6 -3
- autobyteus/agent_team/context/agent_team_context.py +25 -3
- autobyteus/agent_team/context/agent_team_runtime_state.py +11 -8
- autobyteus/agent_team/events/__init__.py +11 -0
- autobyteus/agent_team/events/agent_team_event_dispatcher.py +22 -9
- autobyteus/agent_team/events/agent_team_events.py +16 -0
- autobyteus/agent_team/events/event_store.py +57 -0
- autobyteus/agent_team/factory/agent_team_factory.py +8 -0
- autobyteus/agent_team/handlers/inter_agent_message_request_event_handler.py +18 -2
- autobyteus/agent_team/handlers/lifecycle_agent_team_event_handler.py +21 -5
- autobyteus/agent_team/handlers/process_user_message_event_handler.py +17 -8
- autobyteus/agent_team/handlers/tool_approval_team_event_handler.py +19 -4
- autobyteus/agent_team/runtime/agent_team_runtime.py +41 -10
- autobyteus/agent_team/runtime/agent_team_worker.py +69 -5
- autobyteus/agent_team/status/__init__.py +14 -0
- autobyteus/agent_team/status/agent_team_status.py +18 -0
- autobyteus/agent_team/status/agent_team_status_manager.py +33 -0
- autobyteus/agent_team/status/status_deriver.py +62 -0
- autobyteus/agent_team/status/status_update_utils.py +42 -0
- autobyteus/agent_team/streaming/__init__.py +2 -2
- autobyteus/agent_team/streaming/agent_team_event_notifier.py +10 -10
- autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py +7 -7
- autobyteus/agent_team/streaming/agent_team_stream_events.py +11 -11
- autobyteus/agent_team/system_prompt_processor/__init__.py +6 -0
- autobyteus/agent_team/system_prompt_processor/team_manifest_injector_processor.py +76 -0
- autobyteus/agent_team/task_notification/activation_policy.py +1 -1
- autobyteus/agent_team/task_notification/system_event_driven_agent_task_notifier.py +22 -22
- autobyteus/agent_team/task_notification/task_notification_mode.py +20 -1
- autobyteus/agent_team/utils/wait_for_idle.py +4 -4
- autobyteus/cli/agent_cli.py +18 -10
- autobyteus/cli/agent_team_tui/app.py +18 -15
- autobyteus/cli/agent_team_tui/state.py +21 -23
- autobyteus/cli/agent_team_tui/widgets/agent_list_sidebar.py +15 -15
- autobyteus/cli/agent_team_tui/widgets/focus_pane.py +146 -39
- autobyteus/cli/agent_team_tui/widgets/renderables.py +1 -1
- autobyteus/cli/agent_team_tui/widgets/shared.py +26 -26
- autobyteus/cli/agent_team_tui/widgets/{task_board_panel.py → task_plan_panel.py} +5 -5
- autobyteus/cli/cli_display.py +193 -44
- autobyteus/cli/workflow_tui/app.py +9 -10
- autobyteus/cli/workflow_tui/state.py +14 -16
- autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +15 -15
- autobyteus/cli/workflow_tui/widgets/focus_pane.py +137 -35
- autobyteus/cli/workflow_tui/widgets/renderables.py +1 -1
- autobyteus/cli/workflow_tui/widgets/shared.py +25 -25
- autobyteus/clients/autobyteus_client.py +94 -1
- autobyteus/events/event_types.py +15 -21
- autobyteus/llm/api/autobyteus_llm.py +33 -29
- autobyteus/llm/api/claude_llm.py +142 -36
- autobyteus/llm/api/gemini_llm.py +163 -59
- autobyteus/llm/api/grok_llm.py +1 -1
- autobyteus/llm/api/minimax_llm.py +26 -0
- autobyteus/llm/api/mistral_llm.py +113 -87
- autobyteus/llm/api/ollama_llm.py +9 -42
- autobyteus/llm/api/openai_compatible_llm.py +127 -91
- autobyteus/llm/api/openai_llm.py +3 -3
- autobyteus/llm/api/openai_responses_llm.py +324 -0
- autobyteus/llm/api/zhipu_llm.py +21 -2
- autobyteus/llm/autobyteus_provider.py +70 -60
- autobyteus/llm/base_llm.py +85 -81
- autobyteus/llm/converters/__init__.py +14 -0
- autobyteus/llm/converters/anthropic_tool_call_converter.py +37 -0
- autobyteus/llm/converters/gemini_tool_call_converter.py +57 -0
- autobyteus/llm/converters/mistral_tool_call_converter.py +37 -0
- autobyteus/llm/converters/openai_tool_call_converter.py +38 -0
- autobyteus/llm/extensions/base_extension.py +6 -12
- autobyteus/llm/extensions/token_usage_tracking_extension.py +45 -18
- autobyteus/llm/llm_factory.py +282 -204
- autobyteus/llm/lmstudio_provider.py +60 -49
- autobyteus/llm/models.py +35 -2
- autobyteus/llm/ollama_provider.py +60 -49
- autobyteus/llm/ollama_provider_resolver.py +0 -1
- autobyteus/llm/prompt_renderers/__init__.py +19 -0
- autobyteus/llm/prompt_renderers/anthropic_prompt_renderer.py +104 -0
- autobyteus/llm/prompt_renderers/autobyteus_prompt_renderer.py +19 -0
- autobyteus/llm/prompt_renderers/base_prompt_renderer.py +10 -0
- autobyteus/llm/prompt_renderers/gemini_prompt_renderer.py +63 -0
- autobyteus/llm/prompt_renderers/mistral_prompt_renderer.py +87 -0
- autobyteus/llm/prompt_renderers/ollama_prompt_renderer.py +51 -0
- autobyteus/llm/prompt_renderers/openai_chat_renderer.py +97 -0
- autobyteus/llm/prompt_renderers/openai_responses_renderer.py +101 -0
- autobyteus/llm/providers.py +1 -3
- autobyteus/llm/token_counter/claude_token_counter.py +56 -25
- autobyteus/llm/token_counter/mistral_token_counter.py +12 -8
- autobyteus/llm/token_counter/openai_token_counter.py +24 -5
- autobyteus/llm/token_counter/token_counter_factory.py +12 -5
- autobyteus/llm/utils/llm_config.py +6 -12
- autobyteus/llm/utils/media_payload_formatter.py +27 -20
- autobyteus/llm/utils/messages.py +55 -3
- autobyteus/llm/utils/response_types.py +3 -0
- autobyteus/llm/utils/tool_call_delta.py +31 -0
- autobyteus/memory/__init__.py +32 -0
- autobyteus/memory/active_transcript.py +69 -0
- autobyteus/memory/compaction/__init__.py +9 -0
- autobyteus/memory/compaction/compaction_result.py +8 -0
- autobyteus/memory/compaction/compactor.py +89 -0
- autobyteus/memory/compaction/summarizer.py +11 -0
- autobyteus/memory/compaction_snapshot_builder.py +84 -0
- autobyteus/memory/memory_manager.py +183 -0
- autobyteus/memory/models/__init__.py +14 -0
- autobyteus/memory/models/episodic_item.py +41 -0
- autobyteus/memory/models/memory_types.py +7 -0
- autobyteus/memory/models/raw_trace_item.py +79 -0
- autobyteus/memory/models/semantic_item.py +41 -0
- autobyteus/memory/models/tool_interaction.py +20 -0
- autobyteus/memory/policies/__init__.py +5 -0
- autobyteus/memory/policies/compaction_policy.py +16 -0
- autobyteus/memory/retrieval/__init__.py +7 -0
- autobyteus/memory/retrieval/memory_bundle.py +11 -0
- autobyteus/memory/retrieval/retriever.py +13 -0
- autobyteus/memory/store/__init__.py +7 -0
- autobyteus/memory/store/base_store.py +14 -0
- autobyteus/memory/store/file_store.py +98 -0
- autobyteus/memory/tool_interaction_builder.py +46 -0
- autobyteus/memory/turn_tracker.py +9 -0
- autobyteus/multimedia/audio/api/__init__.py +3 -2
- autobyteus/multimedia/audio/api/autobyteus_audio_client.py +19 -5
- autobyteus/multimedia/audio/api/gemini_audio_client.py +108 -16
- autobyteus/multimedia/audio/api/openai_audio_client.py +112 -0
- autobyteus/multimedia/audio/audio_client_factory.py +84 -9
- autobyteus/multimedia/audio/audio_model.py +2 -1
- autobyteus/multimedia/image/api/autobyteus_image_client.py +19 -5
- autobyteus/multimedia/image/api/gemini_image_client.py +38 -17
- autobyteus/multimedia/image/api/openai_image_client.py +125 -43
- autobyteus/multimedia/image/autobyteus_image_provider.py +2 -1
- autobyteus/multimedia/image/image_client_factory.py +47 -15
- autobyteus/multimedia/image/image_model.py +5 -2
- autobyteus/multimedia/providers.py +3 -2
- autobyteus/skills/loader.py +71 -0
- autobyteus/skills/model.py +11 -0
- autobyteus/skills/registry.py +70 -0
- autobyteus/task_management/__init__.py +43 -20
- autobyteus/task_management/{base_task_board.py → base_task_plan.py} +16 -13
- autobyteus/task_management/converters/__init__.py +2 -2
- autobyteus/task_management/converters/{task_board_converter.py → task_plan_converter.py} +13 -13
- autobyteus/task_management/events.py +7 -7
- autobyteus/task_management/{in_memory_task_board.py → in_memory_task_plan.py} +34 -22
- autobyteus/task_management/schemas/__init__.py +3 -0
- autobyteus/task_management/schemas/task_status_report.py +2 -2
- autobyteus/task_management/schemas/todo_definition.py +15 -0
- autobyteus/task_management/todo.py +29 -0
- autobyteus/task_management/todo_list.py +75 -0
- autobyteus/task_management/tools/__init__.py +24 -8
- autobyteus/task_management/tools/task_tools/__init__.py +19 -0
- autobyteus/task_management/tools/{assign_task_to.py → task_tools/assign_task_to.py} +18 -18
- autobyteus/task_management/tools/{publish_task.py → task_tools/create_task.py} +16 -18
- autobyteus/task_management/tools/{publish_tasks.py → task_tools/create_tasks.py} +19 -19
- autobyteus/task_management/tools/{get_my_tasks.py → task_tools/get_my_tasks.py} +15 -15
- autobyteus/task_management/tools/{get_task_board_status.py → task_tools/get_task_plan_status.py} +16 -16
- autobyteus/task_management/tools/{update_task_status.py → task_tools/update_task_status.py} +16 -16
- autobyteus/task_management/tools/todo_tools/__init__.py +18 -0
- autobyteus/task_management/tools/todo_tools/add_todo.py +78 -0
- autobyteus/task_management/tools/todo_tools/create_todo_list.py +79 -0
- autobyteus/task_management/tools/todo_tools/get_todo_list.py +55 -0
- autobyteus/task_management/tools/todo_tools/update_todo_status.py +85 -0
- autobyteus/tools/__init__.py +43 -52
- autobyteus/tools/base_tool.py +7 -0
- autobyteus/tools/file/__init__.py +9 -0
- autobyteus/tools/file/patch_file.py +149 -0
- autobyteus/tools/file/{file_reader.py → read_file.py} +38 -7
- autobyteus/tools/file/{file_writer.py → write_file.py} +7 -4
- autobyteus/tools/functional_tool.py +53 -14
- autobyteus/tools/mcp/__init__.py +2 -0
- autobyteus/tools/mcp/config_service.py +5 -1
- autobyteus/tools/mcp/server/__init__.py +2 -0
- autobyteus/tools/mcp/server/http_managed_mcp_server.py +1 -1
- autobyteus/tools/mcp/server/websocket_managed_mcp_server.py +141 -0
- autobyteus/tools/mcp/server_instance_manager.py +8 -1
- autobyteus/tools/mcp/tool.py +3 -3
- autobyteus/tools/mcp/tool_registrar.py +5 -2
- autobyteus/tools/mcp/types.py +61 -0
- autobyteus/tools/multimedia/__init__.py +2 -1
- autobyteus/tools/multimedia/audio_tools.py +72 -19
- autobyteus/tools/{download_media_tool.py → multimedia/download_media_tool.py} +21 -7
- autobyteus/tools/multimedia/image_tools.py +248 -64
- autobyteus/tools/multimedia/media_reader_tool.py +1 -1
- autobyteus/tools/operation_executor/journal_manager.py +107 -0
- autobyteus/tools/operation_executor/operation_event_buffer.py +57 -0
- autobyteus/tools/operation_executor/operation_event_producer.py +29 -0
- autobyteus/tools/operation_executor/operation_executor.py +58 -0
- autobyteus/tools/registry/tool_definition.py +108 -14
- autobyteus/tools/registry/tool_registry.py +29 -0
- autobyteus/tools/search/__init__.py +17 -0
- autobyteus/tools/search/base_strategy.py +35 -0
- autobyteus/tools/search/client.py +24 -0
- autobyteus/tools/search/factory.py +81 -0
- autobyteus/tools/search/google_cse_strategy.py +68 -0
- autobyteus/tools/search/providers.py +10 -0
- autobyteus/tools/search/serpapi_strategy.py +65 -0
- autobyteus/tools/search/serper_strategy.py +87 -0
- autobyteus/tools/search_tool.py +83 -0
- autobyteus/tools/skill/load_skill.py +50 -0
- autobyteus/tools/terminal/__init__.py +45 -0
- autobyteus/tools/terminal/ansi_utils.py +32 -0
- autobyteus/tools/terminal/background_process_manager.py +233 -0
- autobyteus/tools/terminal/output_buffer.py +105 -0
- autobyteus/tools/terminal/prompt_detector.py +63 -0
- autobyteus/tools/terminal/pty_session.py +241 -0
- autobyteus/tools/terminal/session_factory.py +20 -0
- autobyteus/tools/terminal/terminal_session_manager.py +226 -0
- autobyteus/tools/terminal/tools/__init__.py +13 -0
- autobyteus/tools/terminal/tools/get_process_output.py +81 -0
- autobyteus/tools/terminal/tools/run_bash.py +109 -0
- autobyteus/tools/terminal/tools/start_background_process.py +104 -0
- autobyteus/tools/terminal/tools/stop_background_process.py +67 -0
- autobyteus/tools/terminal/types.py +54 -0
- autobyteus/tools/terminal/wsl_tmux_session.py +221 -0
- autobyteus/tools/terminal/wsl_utils.py +156 -0
- autobyteus/tools/tool_meta.py +4 -24
- autobyteus/tools/transaction_management/backup_handler.py +48 -0
- autobyteus/tools/transaction_management/operation_lifecycle_manager.py +62 -0
- autobyteus/tools/usage/__init__.py +1 -2
- autobyteus/tools/usage/formatters/__init__.py +17 -1
- autobyteus/tools/usage/formatters/base_formatter.py +8 -0
- autobyteus/tools/usage/formatters/default_xml_schema_formatter.py +2 -2
- autobyteus/tools/usage/formatters/mistral_json_schema_formatter.py +18 -0
- autobyteus/tools/usage/formatters/patch_file_xml_example_formatter.py +64 -0
- autobyteus/tools/usage/formatters/patch_file_xml_schema_formatter.py +31 -0
- autobyteus/tools/usage/formatters/run_bash_xml_example_formatter.py +32 -0
- autobyteus/tools/usage/formatters/run_bash_xml_schema_formatter.py +36 -0
- autobyteus/tools/usage/formatters/write_file_xml_example_formatter.py +53 -0
- autobyteus/tools/usage/formatters/write_file_xml_schema_formatter.py +31 -0
- autobyteus/tools/usage/providers/tool_manifest_provider.py +10 -10
- autobyteus/tools/usage/registries/__init__.py +1 -3
- autobyteus/tools/usage/registries/tool_formatting_registry.py +115 -8
- autobyteus/tools/usage/tool_schema_provider.py +51 -0
- autobyteus/tools/web/__init__.py +4 -0
- autobyteus/tools/web/read_url_tool.py +80 -0
- autobyteus/utils/diff_utils.py +271 -0
- autobyteus/utils/download_utils.py +109 -0
- autobyteus/utils/file_utils.py +57 -2
- autobyteus/utils/gemini_helper.py +56 -0
- autobyteus/utils/gemini_model_mapping.py +71 -0
- autobyteus/utils/llm_output_formatter.py +75 -0
- autobyteus/utils/tool_call_format.py +36 -0
- autobyteus/workflow/agentic_workflow.py +3 -3
- autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +2 -2
- autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +2 -2
- autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +2 -2
- autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +4 -11
- autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +6 -6
- autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +2 -2
- autobyteus/workflow/context/workflow_context.py +3 -3
- autobyteus/workflow/context/workflow_runtime_state.py +5 -5
- autobyteus/workflow/events/workflow_event_dispatcher.py +5 -5
- autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +3 -3
- autobyteus/workflow/handlers/process_user_message_event_handler.py +5 -5
- autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +2 -2
- autobyteus/workflow/runtime/workflow_runtime.py +8 -8
- autobyteus/workflow/runtime/workflow_worker.py +3 -3
- autobyteus/workflow/status/__init__.py +11 -0
- autobyteus/workflow/status/workflow_status.py +19 -0
- autobyteus/workflow/status/workflow_status_manager.py +48 -0
- autobyteus/workflow/streaming/__init__.py +2 -2
- autobyteus/workflow/streaming/workflow_event_notifier.py +7 -7
- autobyteus/workflow/streaming/workflow_stream_event_payloads.py +4 -4
- autobyteus/workflow/streaming/workflow_stream_events.py +3 -3
- autobyteus/workflow/utils/wait_for_idle.py +4 -4
- autobyteus-1.2.3.dist-info/METADATA +293 -0
- autobyteus-1.2.3.dist-info/RECORD +600 -0
- {autobyteus-1.2.0.dist-info → autobyteus-1.2.3.dist-info}/WHEEL +1 -1
- {autobyteus-1.2.0.dist-info → autobyteus-1.2.3.dist-info}/top_level.txt +0 -1
- autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +0 -57
- autobyteus/agent/hooks/__init__.py +0 -16
- autobyteus/agent/hooks/base_phase_hook.py +0 -78
- autobyteus/agent/hooks/hook_definition.py +0 -36
- autobyteus/agent/hooks/hook_meta.py +0 -37
- autobyteus/agent/hooks/hook_registry.py +0 -106
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +0 -103
- autobyteus/agent/phases/__init__.py +0 -18
- autobyteus/agent/phases/discover.py +0 -53
- autobyteus/agent/phases/manager.py +0 -265
- autobyteus/agent/phases/transition_decorator.py +0 -40
- autobyteus/agent/phases/transition_info.py +0 -33
- autobyteus/agent/remote_agent.py +0 -244
- autobyteus/agent/workspace/workspace_definition.py +0 -36
- autobyteus/agent/workspace/workspace_meta.py +0 -37
- autobyteus/agent/workspace/workspace_registry.py +0 -72
- autobyteus/agent_team/bootstrap_steps/agent_team_runtime_queue_initialization_step.py +0 -25
- autobyteus/agent_team/bootstrap_steps/coordinator_prompt_preparation_step.py +0 -85
- autobyteus/agent_team/phases/__init__.py +0 -11
- autobyteus/agent_team/phases/agent_team_operational_phase.py +0 -19
- autobyteus/agent_team/phases/agent_team_phase_manager.py +0 -48
- autobyteus/llm/api/bedrock_llm.py +0 -92
- autobyteus/llm/api/groq_llm.py +0 -94
- autobyteus/llm/api/nvidia_llm.py +0 -108
- autobyteus/llm/utils/token_pricing_config.py +0 -87
- autobyteus/person/examples/sample_persons.py +0 -14
- autobyteus/person/examples/sample_roles.py +0 -14
- autobyteus/person/person.py +0 -29
- autobyteus/person/role.py +0 -14
- autobyteus/rpc/__init__.py +0 -73
- autobyteus/rpc/client/__init__.py +0 -17
- autobyteus/rpc/client/abstract_client_connection.py +0 -124
- autobyteus/rpc/client/client_connection_manager.py +0 -153
- autobyteus/rpc/client/sse_client_connection.py +0 -306
- autobyteus/rpc/client/stdio_client_connection.py +0 -280
- autobyteus/rpc/config/__init__.py +0 -13
- autobyteus/rpc/config/agent_server_config.py +0 -153
- autobyteus/rpc/config/agent_server_registry.py +0 -152
- autobyteus/rpc/hosting.py +0 -244
- autobyteus/rpc/protocol.py +0 -244
- autobyteus/rpc/server/__init__.py +0 -20
- autobyteus/rpc/server/agent_server_endpoint.py +0 -181
- autobyteus/rpc/server/base_method_handler.py +0 -40
- autobyteus/rpc/server/method_handlers.py +0 -259
- autobyteus/rpc/server/sse_server_handler.py +0 -182
- autobyteus/rpc/server/stdio_server_handler.py +0 -151
- autobyteus/rpc/server_main.py +0 -198
- autobyteus/rpc/transport_type.py +0 -13
- autobyteus/tools/bash/__init__.py +0 -2
- autobyteus/tools/bash/bash_executor.py +0 -100
- autobyteus/tools/browser/__init__.py +0 -2
- autobyteus/tools/browser/session_aware/__init__.py +0 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +0 -75
- autobyteus/tools/browser/session_aware/browser_session_aware_tool.py +0 -30
- autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +0 -154
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +0 -89
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +0 -107
- autobyteus/tools/browser/session_aware/factory/__init__.py +0 -0
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_web_element_trigger_factory.py +0 -14
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_reader_factory.py +0 -26
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_screenshot_taker_factory.py +0 -14
- autobyteus/tools/browser/session_aware/shared_browser_session.py +0 -11
- autobyteus/tools/browser/session_aware/shared_browser_session_manager.py +0 -25
- autobyteus/tools/browser/session_aware/web_element_action.py +0 -20
- autobyteus/tools/browser/standalone/__init__.py +0 -6
- autobyteus/tools/browser/standalone/factory/__init__.py +0 -0
- autobyteus/tools/browser/standalone/factory/webpage_reader_factory.py +0 -25
- autobyteus/tools/browser/standalone/factory/webpage_screenshot_taker_factory.py +0 -14
- autobyteus/tools/browser/standalone/navigate_to.py +0 -80
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +0 -97
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +0 -165
- autobyteus/tools/browser/standalone/webpage_reader.py +0 -101
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +0 -101
- autobyteus/tools/file/file_editor.py +0 -200
- autobyteus/tools/google_search.py +0 -149
- autobyteus/tools/timer.py +0 -171
- autobyteus/tools/usage/parsers/__init__.py +0 -22
- autobyteus/tools/usage/parsers/_json_extractor.py +0 -99
- autobyteus/tools/usage/parsers/_string_decoders.py +0 -18
- autobyteus/tools/usage/parsers/anthropic_xml_tool_usage_parser.py +0 -10
- autobyteus/tools/usage/parsers/base_parser.py +0 -41
- autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +0 -83
- autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +0 -316
- autobyteus/tools/usage/parsers/exceptions.py +0 -13
- autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +0 -77
- autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +0 -149
- autobyteus/tools/usage/parsers/provider_aware_tool_usage_parser.py +0 -59
- autobyteus/tools/usage/registries/tool_usage_parser_registry.py +0 -62
- autobyteus/workflow/phases/__init__.py +0 -11
- autobyteus/workflow/phases/workflow_operational_phase.py +0 -19
- autobyteus/workflow/phases/workflow_phase_manager.py +0 -48
- autobyteus-1.2.0.dist-info/METADATA +0 -205
- autobyteus-1.2.0.dist-info/RECORD +0 -496
- examples/__init__.py +0 -1
- examples/agent_team/__init__.py +0 -1
- examples/discover_phase_transitions.py +0 -104
- examples/run_browser_agent.py +0 -262
- examples/run_google_slides_agent.py +0 -287
- examples/run_mcp_browser_client.py +0 -174
- examples/run_mcp_google_slides_client.py +0 -270
- examples/run_mcp_list_tools.py +0 -189
- examples/run_poem_writer.py +0 -284
- examples/run_sqlite_agent.py +0 -295
- /autobyteus/{person → skills}/__init__.py +0 -0
- /autobyteus/{person/examples → tools/skill}/__init__.py +0 -0
- {autobyteus-1.2.0.dist-info → autobyteus-1.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,12 +5,18 @@ from typing import Dict, Optional
|
|
|
5
5
|
from autobyteus.llm.providers import LLMProvider
|
|
6
6
|
from autobyteus.utils.singleton import SingletonMeta
|
|
7
7
|
from .tool_formatter_pair import ToolFormatterPair
|
|
8
|
+
from autobyteus.utils.tool_call_format import resolve_tool_call_format
|
|
8
9
|
|
|
9
10
|
# Import all necessary formatters
|
|
10
11
|
from autobyteus.tools.usage.formatters import (
|
|
11
12
|
DefaultJsonSchemaFormatter, OpenAiJsonSchemaFormatter, AnthropicJsonSchemaFormatter, GeminiJsonSchemaFormatter,
|
|
12
13
|
DefaultJsonExampleFormatter, OpenAiJsonExampleFormatter, AnthropicJsonExampleFormatter, GeminiJsonExampleFormatter,
|
|
13
|
-
DefaultXmlSchemaFormatter, DefaultXmlExampleFormatter
|
|
14
|
+
DefaultXmlSchemaFormatter, DefaultXmlExampleFormatter,
|
|
15
|
+
BaseSchemaFormatter, BaseExampleFormatter,
|
|
16
|
+
# Tool-specific formatters
|
|
17
|
+
WriteFileXmlSchemaFormatter, WriteFileXmlExampleFormatter,
|
|
18
|
+
PatchFileXmlSchemaFormatter, PatchFileXmlExampleFormatter,
|
|
19
|
+
RunBashXmlSchemaFormatter, RunBashXmlExampleFormatter,
|
|
14
20
|
)
|
|
15
21
|
|
|
16
22
|
logger = logging.getLogger(__name__)
|
|
@@ -19,6 +25,9 @@ class ToolFormattingRegistry(metaclass=SingletonMeta):
|
|
|
19
25
|
"""
|
|
20
26
|
A consolidated registry that maps an LLMProvider directly to its required
|
|
21
27
|
ToolFormatterPair, which contains both schema and example formatters.
|
|
28
|
+
|
|
29
|
+
Also supports tool-specific formatter pairs that take priority over provider defaults.
|
|
30
|
+
Priority cascade: tool-specific → provider-specific → default
|
|
22
31
|
"""
|
|
23
32
|
|
|
24
33
|
def __init__(self):
|
|
@@ -38,28 +47,126 @@ class ToolFormattingRegistry(metaclass=SingletonMeta):
|
|
|
38
47
|
self._default_pair = ToolFormatterPair(DefaultJsonSchemaFormatter(), DefaultJsonExampleFormatter())
|
|
39
48
|
# A specific pair for the XML override
|
|
40
49
|
self._xml_override_pair = ToolFormatterPair(DefaultXmlSchemaFormatter(), DefaultXmlExampleFormatter())
|
|
50
|
+
# Tool-specific formatter pairs (tool_name -> ToolFormatterPair)
|
|
51
|
+
self._tool_pairs: Dict[str, ToolFormatterPair] = {}
|
|
52
|
+
|
|
53
|
+
# Register tool-specific formatters
|
|
54
|
+
self._register_tool_formatters()
|
|
41
55
|
|
|
42
56
|
logger.info("ToolFormattingRegistry initialized with direct provider-to-formatter mappings.")
|
|
43
57
|
|
|
44
|
-
def
|
|
58
|
+
def _register_tool_formatters(self) -> None:
|
|
59
|
+
"""Register built-in tool-specific formatters."""
|
|
60
|
+
# write_file uses standard <tool name="write_file"> syntax with custom sentinel instructions
|
|
61
|
+
self._tool_pairs["write_file"] = ToolFormatterPair(
|
|
62
|
+
WriteFileXmlSchemaFormatter(),
|
|
63
|
+
WriteFileXmlExampleFormatter()
|
|
64
|
+
)
|
|
65
|
+
# patch_file uses standard <tool name="patch_file"> syntax with custom sentinel instructions
|
|
66
|
+
self._tool_pairs["patch_file"] = ToolFormatterPair(
|
|
67
|
+
PatchFileXmlSchemaFormatter(),
|
|
68
|
+
PatchFileXmlExampleFormatter()
|
|
69
|
+
)
|
|
70
|
+
# run_bash uses shorthand <run_bash> syntax
|
|
71
|
+
#self._tool_pairs["run_bash"] = ToolFormatterPair(
|
|
72
|
+
# RunBashXmlSchemaFormatter(),
|
|
73
|
+
# RunBashXmlExampleFormatter()
|
|
74
|
+
#)
|
|
75
|
+
|
|
76
|
+
def register_tool_formatter(self, tool_name: str, formatter_pair: ToolFormatterPair) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Register a tool-specific formatter pair.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
tool_name: The name of the tool (e.g., 'write_file').
|
|
82
|
+
formatter_pair: The formatter pair to use for this tool.
|
|
83
|
+
"""
|
|
84
|
+
self._tool_pairs[tool_name] = formatter_pair
|
|
85
|
+
logger.info(f"Registered tool-specific formatter for '{tool_name}'.")
|
|
86
|
+
|
|
87
|
+
def get_formatter_pair_for_tool(
|
|
88
|
+
self,
|
|
89
|
+
tool_name: str,
|
|
90
|
+
provider: Optional[LLMProvider]
|
|
91
|
+
) -> ToolFormatterPair:
|
|
92
|
+
"""
|
|
93
|
+
Get the formatter pair for a specific tool with priority cascade.
|
|
94
|
+
|
|
95
|
+
Priority:
|
|
96
|
+
1. Tool-specific pair (if registered)
|
|
97
|
+
2. Provider-specific pair (if provider known)
|
|
98
|
+
3. Default pair
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
tool_name: The name of the tool.
|
|
102
|
+
provider: The LLM provider.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The appropriate ToolFormatterPair.
|
|
106
|
+
"""
|
|
107
|
+
if tool_name in self._tool_pairs:
|
|
108
|
+
logger.debug(f"Using tool-specific formatter for '{tool_name}'.")
|
|
109
|
+
return self._tool_pairs[tool_name]
|
|
110
|
+
return self.get_formatter_pair(provider)
|
|
111
|
+
|
|
112
|
+
def get_formatter_pair(self, provider: Optional[LLMProvider]) -> ToolFormatterPair:
|
|
45
113
|
"""
|
|
46
|
-
Retrieves the appropriate formatting pair for a given provider, honoring the
|
|
114
|
+
Retrieves the appropriate formatting pair for a given provider, honoring the env format override.
|
|
47
115
|
|
|
48
116
|
Args:
|
|
49
117
|
provider: The LLMProvider enum member.
|
|
50
|
-
use_xml_tool_format: If True, forces the use of XML formatters.
|
|
51
118
|
|
|
52
119
|
Returns:
|
|
53
120
|
The corresponding ToolFormatterPair instance.
|
|
54
121
|
"""
|
|
55
|
-
|
|
56
|
-
|
|
122
|
+
format_override = resolve_tool_call_format()
|
|
123
|
+
if format_override == "xml":
|
|
124
|
+
logger.info("Tool format resolved to XML (env override).")
|
|
57
125
|
return self._xml_override_pair
|
|
126
|
+
if format_override == "json":
|
|
127
|
+
logger.info("Tool format resolved to JSON (env override).")
|
|
128
|
+
return self._default_pair
|
|
129
|
+
if format_override in {"sentinel", "api_tool_call"}:
|
|
130
|
+
logger.info(
|
|
131
|
+
"Tool format '%s' is not supported by formatter registry. "
|
|
132
|
+
"Falling back to JSON formatters.",
|
|
133
|
+
format_override,
|
|
134
|
+
)
|
|
135
|
+
return self._default_pair
|
|
58
136
|
|
|
59
137
|
if provider and provider in self._pairs:
|
|
60
138
|
pair = self._pairs[provider]
|
|
61
|
-
logger.
|
|
139
|
+
logger.info(
|
|
140
|
+
"Tool format resolved by provider '%s' to %s.",
|
|
141
|
+
provider.name,
|
|
142
|
+
"XML" if pair is self._xml_override_pair else "JSON",
|
|
143
|
+
)
|
|
62
144
|
return pair
|
|
63
145
|
|
|
64
|
-
logger.
|
|
146
|
+
logger.info(
|
|
147
|
+
"Tool format resolved by default to JSON (provider=%s).",
|
|
148
|
+
provider.name if provider else "Unknown",
|
|
149
|
+
)
|
|
65
150
|
return self._default_pair
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def register_tool_formatter(
|
|
154
|
+
tool_name: str,
|
|
155
|
+
schema_formatter: BaseSchemaFormatter,
|
|
156
|
+
example_formatter: BaseExampleFormatter
|
|
157
|
+
) -> None:
|
|
158
|
+
"""
|
|
159
|
+
Registers a custom schema and example formatter for a specific tool.
|
|
160
|
+
|
|
161
|
+
This allows developers to define exactly how a tool's schema and usage example
|
|
162
|
+
should be presented to the LLM, overriding default provider-specific behavior.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
tool_name: The name of the tool (must match the @tool name).
|
|
166
|
+
schema_formatter: An instance of a class inheriting from BaseSchemaFormatter.
|
|
167
|
+
example_formatter: An instance of a class inheriting from BaseExampleFormatter.
|
|
168
|
+
"""
|
|
169
|
+
registry = ToolFormattingRegistry()
|
|
170
|
+
pair = ToolFormatterPair(schema_formatter, example_formatter)
|
|
171
|
+
registry.register_tool_formatter(tool_name, pair)
|
|
172
|
+
logger.info(f"Registered custom formatter pair for tool '{tool_name}' via facade.")
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/tools/usage/tool_schema_provider.py
|
|
2
|
+
"""
|
|
3
|
+
Provider-aware tool schema builder for API tool calls.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Iterable, List, Dict, Optional
|
|
9
|
+
|
|
10
|
+
from autobyteus.llm.providers import LLMProvider
|
|
11
|
+
from autobyteus.tools.registry import default_tool_registry
|
|
12
|
+
from autobyteus.tools.usage.formatters.anthropic_json_schema_formatter import AnthropicJsonSchemaFormatter
|
|
13
|
+
from autobyteus.tools.usage.formatters.gemini_json_schema_formatter import GeminiJsonSchemaFormatter
|
|
14
|
+
from autobyteus.tools.usage.formatters.openai_json_schema_formatter import OpenAiJsonSchemaFormatter
|
|
15
|
+
from autobyteus.tools.registry.tool_definition import ToolDefinition
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ToolSchemaProvider:
|
|
21
|
+
"""Builds API tool schemas for a provider."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, registry=default_tool_registry):
|
|
24
|
+
self._registry = registry
|
|
25
|
+
|
|
26
|
+
def build_schema(
|
|
27
|
+
self,
|
|
28
|
+
tool_names: Iterable[str],
|
|
29
|
+
provider: Optional[LLMProvider],
|
|
30
|
+
) -> List[Dict]:
|
|
31
|
+
tool_definitions: List[ToolDefinition] = []
|
|
32
|
+
for name in tool_names:
|
|
33
|
+
tool_def = self._registry.get_tool_definition(name)
|
|
34
|
+
if tool_def:
|
|
35
|
+
tool_definitions.append(tool_def)
|
|
36
|
+
else:
|
|
37
|
+
logger.warning("Tool '%s' not found in registry.", name)
|
|
38
|
+
|
|
39
|
+
if not tool_definitions:
|
|
40
|
+
return []
|
|
41
|
+
|
|
42
|
+
formatter = self._select_formatter(provider)
|
|
43
|
+
return [formatter.provide(td) for td in tool_definitions]
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def _select_formatter(provider: Optional[LLMProvider]):
|
|
47
|
+
if provider == LLMProvider.ANTHROPIC:
|
|
48
|
+
return AnthropicJsonSchemaFormatter()
|
|
49
|
+
if provider == LLMProvider.GEMINI:
|
|
50
|
+
return GeminiJsonSchemaFormatter()
|
|
51
|
+
return OpenAiJsonSchemaFormatter()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import aiohttp
|
|
3
|
+
from typing import Optional, TYPE_CHECKING
|
|
4
|
+
from autobyteus.tools.base_tool import BaseTool
|
|
5
|
+
from autobyteus.tools.tool_category import ToolCategory
|
|
6
|
+
from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
|
|
7
|
+
from autobyteus.utils.html_cleaner import clean, CleaningMode
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from autobyteus.agent.context import AgentContext
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class ReadUrl(BaseTool):
|
|
15
|
+
"""
|
|
16
|
+
Lightweight URL content reader that fetches web page content using aiohttp.
|
|
17
|
+
Optimized for fast, efficient reading of static content by extracting pure text.
|
|
18
|
+
"""
|
|
19
|
+
CATEGORY = ToolCategory.WEB
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def get_name(cls) -> str:
|
|
23
|
+
return "read_url"
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def get_description(cls) -> str:
|
|
27
|
+
return (
|
|
28
|
+
"Reads the content of a specific URL using a lightweight HTTP client. "
|
|
29
|
+
"Faster and more efficient than using a browser tool for static pages. "
|
|
30
|
+
"Returns cleaned text content optimized for reading."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def get_argument_schema(cls) -> Optional[ParameterSchema]:
|
|
35
|
+
schema = ParameterSchema()
|
|
36
|
+
schema.add_parameter(ParameterDefinition(
|
|
37
|
+
name="url",
|
|
38
|
+
param_type=ParameterType.STRING,
|
|
39
|
+
description="The URL of the webpage to read.",
|
|
40
|
+
required=True
|
|
41
|
+
))
|
|
42
|
+
schema.add_parameter(ParameterDefinition(
|
|
43
|
+
name="output_format",
|
|
44
|
+
param_type=ParameterType.STRING,
|
|
45
|
+
description="The desired output format: 'text' (default) or 'html'. 'text' returns cleaned text content, 'html' returns cleaned HTML.",
|
|
46
|
+
required=False,
|
|
47
|
+
default_value="text",
|
|
48
|
+
enum_values=["text", "html"]
|
|
49
|
+
))
|
|
50
|
+
return schema
|
|
51
|
+
|
|
52
|
+
async def _execute(self, context: 'AgentContext', url: str, output_format: str = "text") -> str:
|
|
53
|
+
logger.info(f"Executing read_url for agent {context.agent_id} with URL: '{url}'")
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
async with aiohttp.ClientSession() as session:
|
|
57
|
+
# aiohttp handles redirects by default (up to 10)
|
|
58
|
+
async with session.get(url, timeout=30) as response:
|
|
59
|
+
if response.status != 200:
|
|
60
|
+
error_msg = f"Failed to fetch content from {url}. Status code: {response.status}"
|
|
61
|
+
logger.error(error_msg)
|
|
62
|
+
return error_msg
|
|
63
|
+
|
|
64
|
+
html_content = await response.text()
|
|
65
|
+
|
|
66
|
+
# Use appropriate cleaning mode based on requested format
|
|
67
|
+
mode = CleaningMode.TEXT_CONTENT_FOCUSED if output_format == "text" else CleaningMode.THOROUGH
|
|
68
|
+
cleaned_content = clean(html_content, mode=mode)
|
|
69
|
+
|
|
70
|
+
if not cleaned_content.strip():
|
|
71
|
+
return f"Successfully fetched content from {url}, but the cleaned result was empty."
|
|
72
|
+
|
|
73
|
+
return cleaned_content
|
|
74
|
+
|
|
75
|
+
except aiohttp.ClientError as e:
|
|
76
|
+
logger.error(f"Network error reading URL '{url}': {e}", exc_info=True)
|
|
77
|
+
return f"Error reading URL '{url}': Network error ({str(e)})"
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error(f"Unexpected error reading URL '{url}': {e}", exc_info=True)
|
|
80
|
+
return f"Error reading URL '{url}': {str(e)}"
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified diff utilities for applying patches to text content.
|
|
3
|
+
"""
|
|
4
|
+
import re
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
_HUNK_HEADER_RE = re.compile(r"^@@ -(?P<old_start>\d+)(?:,(?P<old_count>\d+))? \+(?P<new_start>\d+)(?:,(?P<new_count>\d+))? @@")
|
|
11
|
+
_GIT_HEADER_PREFIXES = (
|
|
12
|
+
"diff --git ",
|
|
13
|
+
"index ",
|
|
14
|
+
"new file mode ",
|
|
15
|
+
"deleted file mode ",
|
|
16
|
+
"old mode ",
|
|
17
|
+
"new mode ",
|
|
18
|
+
"similarity index ",
|
|
19
|
+
"dissimilarity index ",
|
|
20
|
+
"rename from ",
|
|
21
|
+
"rename to ",
|
|
22
|
+
"copy from ",
|
|
23
|
+
"copy to ",
|
|
24
|
+
"binary files ",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PatchApplicationError(ValueError):
|
|
29
|
+
"""Raised when a unified diff patch cannot be applied to the target content."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def apply_unified_diff(
|
|
33
|
+
original_lines: List[str],
|
|
34
|
+
patch: str,
|
|
35
|
+
fuzz_factor: int = 0,
|
|
36
|
+
ignore_whitespace: bool = False
|
|
37
|
+
) -> List[str]:
|
|
38
|
+
"""Applies a unified diff patch to the provided original lines and returns the patched lines.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
original_lines: List of strings representing the original content lines (with line endings preserved).
|
|
42
|
+
patch: Unified diff patch string describing the edits to apply.
|
|
43
|
+
fuzz_factor: Number of lines to search up/down if exact line number match fails.
|
|
44
|
+
ignore_whitespace: If True, ignores leading/trailing whitespace when matching context.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
List of strings representing the patched content lines.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
PatchApplicationError: If the patch content cannot be applied cleanly.
|
|
51
|
+
"""
|
|
52
|
+
if not patch or not patch.strip():
|
|
53
|
+
raise PatchApplicationError("Patch content is empty; nothing to apply.")
|
|
54
|
+
|
|
55
|
+
patched_lines: List[str] = []
|
|
56
|
+
orig_idx = 0
|
|
57
|
+
patch_lines = patch.splitlines(keepends=True)
|
|
58
|
+
line_idx = 0
|
|
59
|
+
|
|
60
|
+
def lines_match(l1: str, l2: str, *, allow_eof_newline_mismatch: bool = False) -> bool:
|
|
61
|
+
if ignore_whitespace:
|
|
62
|
+
return l1.strip() == l2.strip()
|
|
63
|
+
if l1 == l2:
|
|
64
|
+
return True
|
|
65
|
+
if allow_eof_newline_mismatch:
|
|
66
|
+
return l1.rstrip('\n') == l2.rstrip('\n')
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
while line_idx < len(patch_lines):
|
|
70
|
+
line = patch_lines[line_idx]
|
|
71
|
+
|
|
72
|
+
if line.startswith('---') or line.startswith('+++'):
|
|
73
|
+
logger.debug("apply_unified_diff: skipping diff header line '%s'.", line.strip())
|
|
74
|
+
line_idx += 1
|
|
75
|
+
continue
|
|
76
|
+
stripped_line = line.lstrip().lower()
|
|
77
|
+
if any(stripped_line.startswith(prefix) for prefix in _GIT_HEADER_PREFIXES):
|
|
78
|
+
logger.debug("apply_unified_diff: skipping git diff header line '%s'.", line.strip())
|
|
79
|
+
line_idx += 1
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
if not line.startswith('@@'):
|
|
83
|
+
stripped = line.strip()
|
|
84
|
+
if stripped == '':
|
|
85
|
+
# Handle empty lines between hunks or at start as noise, but
|
|
86
|
+
# legacy behavior might have been to append them?
|
|
87
|
+
# The previous loop skipped them if start of file (implicit in while loop start?),
|
|
88
|
+
# but inside the hunk loop it appended them.
|
|
89
|
+
# Here we are searching for hunk headers.
|
|
90
|
+
line_idx += 1
|
|
91
|
+
continue
|
|
92
|
+
raise PatchApplicationError(f"Unexpected content outside of hunk header: '{stripped}'.")
|
|
93
|
+
|
|
94
|
+
match = _HUNK_HEADER_RE.match(line)
|
|
95
|
+
if not match:
|
|
96
|
+
raise PatchApplicationError(f"Malformed hunk header: '{line.strip()}'.")
|
|
97
|
+
|
|
98
|
+
old_start = int(match.group('old_start'))
|
|
99
|
+
old_count = int(match.group('old_count') or '1')
|
|
100
|
+
new_start = int(match.group('new_start'))
|
|
101
|
+
new_count = int(match.group('new_count') or '1')
|
|
102
|
+
logger.debug("apply_unified_diff: processing hunk old_start=%s old_count=%s new_start=%s new_count=%s.",
|
|
103
|
+
old_start, old_count, new_start, new_count)
|
|
104
|
+
|
|
105
|
+
line_idx += 1 # Move past header
|
|
106
|
+
|
|
107
|
+
# Capture the hunk body
|
|
108
|
+
hunk_body = []
|
|
109
|
+
while line_idx < len(patch_lines):
|
|
110
|
+
h_line = patch_lines[line_idx]
|
|
111
|
+
if h_line.startswith('@@'):
|
|
112
|
+
break
|
|
113
|
+
hunk_body.append(h_line)
|
|
114
|
+
line_idx += 1
|
|
115
|
+
|
|
116
|
+
# Extract expected original content from hunk
|
|
117
|
+
# We process the hunk body to confirm what we expect to see in original_lines
|
|
118
|
+
refined_expected_orig = []
|
|
119
|
+
h_i = 0
|
|
120
|
+
while h_i < len(hunk_body):
|
|
121
|
+
h_line = hunk_body[h_i]
|
|
122
|
+
if h_line.startswith(' ') or h_line.startswith('-'):
|
|
123
|
+
content = h_line[1:]
|
|
124
|
+
# Check next line for '\ No newline...' to handle EOF correctly during match
|
|
125
|
+
if h_i + 1 < len(hunk_body) and hunk_body[h_i+1].startswith('\\ No newline'):
|
|
126
|
+
content = content.rstrip('\n')
|
|
127
|
+
elif h_i + 1 == len(hunk_body) and not content.endswith('\n'):
|
|
128
|
+
# Last line of patch might legitimately not have newline?
|
|
129
|
+
# Usually lines have newlines. If it's a file without newline at end, diff shows \ No newline.
|
|
130
|
+
pass
|
|
131
|
+
refined_expected_orig.append(content)
|
|
132
|
+
elif h_line.startswith('+'):
|
|
133
|
+
pass
|
|
134
|
+
elif h_line.startswith('\\'):
|
|
135
|
+
pass
|
|
136
|
+
elif h_line.strip() == '':
|
|
137
|
+
# Treat bare empty line as non-match content (noise/insert)?
|
|
138
|
+
# Previous code appended it to output but didn't check against input.
|
|
139
|
+
# So we do NOT add to refined_expected_orig.
|
|
140
|
+
pass
|
|
141
|
+
else:
|
|
142
|
+
raise PatchApplicationError(f"Unsupported patch line: '{h_line.strip()}'.")
|
|
143
|
+
h_i += 1
|
|
144
|
+
|
|
145
|
+
expected_count = len(refined_expected_orig)
|
|
146
|
+
|
|
147
|
+
# Fuzzy Search for Match
|
|
148
|
+
# Ideally target is old_start - 1 (converting 1-based old_start to 0-based index)
|
|
149
|
+
target_idx_base = old_start - 1 if old_start > 0 else 0
|
|
150
|
+
|
|
151
|
+
found_idx = -1
|
|
152
|
+
|
|
153
|
+
# Generate search offsets: 0, -1, 1, -2, 2, ...
|
|
154
|
+
offsets = [0]
|
|
155
|
+
for f in range(1, fuzz_factor + 1):
|
|
156
|
+
offsets.append(-f)
|
|
157
|
+
offsets.append(f)
|
|
158
|
+
|
|
159
|
+
for offset in offsets:
|
|
160
|
+
candidate_idx = target_idx_base + offset
|
|
161
|
+
|
|
162
|
+
# Constraints:
|
|
163
|
+
# 1. Must proceed forward from where we left off (orig_idx)
|
|
164
|
+
# 2. Must not go beyond EOF (handled by slicing)
|
|
165
|
+
if candidate_idx < orig_idx:
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
# Check if this candidate location matches expected_orig
|
|
169
|
+
# We need to verify that original_lines[candidate_idx : candidate_idx + expected_count] matches refined_expected_orig
|
|
170
|
+
|
|
171
|
+
if candidate_idx + expected_count > len(original_lines):
|
|
172
|
+
# Ensure we don't go out of bounds (though partial match at EOF might be a thing? No, hunk must match fully)
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
match_success = True
|
|
176
|
+
for k in range(expected_count):
|
|
177
|
+
# Note: lines_match handles whitespace if needed.
|
|
178
|
+
is_eof_line = (
|
|
179
|
+
candidate_idx + expected_count == len(original_lines)
|
|
180
|
+
and k == expected_count - 1
|
|
181
|
+
)
|
|
182
|
+
if not lines_match(
|
|
183
|
+
original_lines[candidate_idx + k],
|
|
184
|
+
refined_expected_orig[k],
|
|
185
|
+
allow_eof_newline_mismatch=is_eof_line,
|
|
186
|
+
):
|
|
187
|
+
match_success = False
|
|
188
|
+
break
|
|
189
|
+
|
|
190
|
+
if match_success:
|
|
191
|
+
found_idx = candidate_idx
|
|
192
|
+
break
|
|
193
|
+
|
|
194
|
+
if found_idx == -1:
|
|
195
|
+
raise PatchApplicationError(f"Could not find context for hunk starting at {old_start} (fuzz={fuzz_factor}).")
|
|
196
|
+
|
|
197
|
+
# Apply changes
|
|
198
|
+
|
|
199
|
+
# 1. Copy lines from orig_idx to found_idx (these are the lines BEFORE the hunk that we skipped over/accepted)
|
|
200
|
+
patched_lines.extend(original_lines[orig_idx:found_idx])
|
|
201
|
+
|
|
202
|
+
# 2. Process hunk body to generate new lines
|
|
203
|
+
h_i = 0
|
|
204
|
+
while h_i < len(hunk_body):
|
|
205
|
+
h_line = hunk_body[h_i]
|
|
206
|
+
if h_line.startswith(' ') or h_line.startswith('-'):
|
|
207
|
+
# Valid context or removal - we skip them in output (they are either preserved via context or removed)
|
|
208
|
+
# If context (' '), we should actually append the ORIGINAL line to allow for fuzziness?
|
|
209
|
+
# Standard patch behavior: if fuzz matches, we output the *hunk's* version of the line?
|
|
210
|
+
# OR we output the *original* file's version?
|
|
211
|
+
# `patch` man page says: "patch takes the context from the diff file"
|
|
212
|
+
# Wait, if we fuzzy matched, likely we want to keep what was in the file unless the patch explicitly changes it.
|
|
213
|
+
# However, typically ' ' lines are just copied.
|
|
214
|
+
# If we use `patched_lines.append(original_lines[found_idx + handled_count])`, that preserves the file's indentation/whitespace.
|
|
215
|
+
# If we use `h_line[1:]`, we enforce the patch's indentation.
|
|
216
|
+
# Requirement: "Ignore Whitespace... Allow context comparisons to ignore...".
|
|
217
|
+
# If the patch "autocorrects" indentation in context lines, we probably want to KEEP the file's original indentation
|
|
218
|
+
# for context lines, so we don't accidentally "fix" them if we aren't touching them.
|
|
219
|
+
# Unified diff: ' ' means "unchanged". So we should emit the *original* line.
|
|
220
|
+
|
|
221
|
+
if h_line.startswith(' '):
|
|
222
|
+
# Find which original line this corresponds to.
|
|
223
|
+
# We are iterating h_i through hunk_body.
|
|
224
|
+
# We need to track our position in the matched original block.
|
|
225
|
+
# Let's track `current_match_offset`
|
|
226
|
+
pass
|
|
227
|
+
elif h_line.startswith('+'):
|
|
228
|
+
pass
|
|
229
|
+
h_i += 1
|
|
230
|
+
|
|
231
|
+
# Let's redo step 2 cleanly to handle the ' ' vs '-' vs '+' and the '\ No newline' logic.
|
|
232
|
+
|
|
233
|
+
matched_orig_offset = 0 # Offset into the matched block of original_lines [found_idx : found_idx+expected_count]
|
|
234
|
+
|
|
235
|
+
h_i = 0
|
|
236
|
+
while h_i < len(hunk_body):
|
|
237
|
+
h_line = hunk_body[h_i]
|
|
238
|
+
|
|
239
|
+
if h_line.startswith(' '):
|
|
240
|
+
# Context line: preserve original content from file
|
|
241
|
+
if found_idx + matched_orig_offset < len(original_lines):
|
|
242
|
+
patched_lines.append(original_lines[found_idx + matched_orig_offset])
|
|
243
|
+
matched_orig_offset += 1
|
|
244
|
+
|
|
245
|
+
elif h_line.startswith('-'):
|
|
246
|
+
# Removal: skip original content
|
|
247
|
+
matched_orig_offset += 1
|
|
248
|
+
|
|
249
|
+
elif h_line.startswith('+'):
|
|
250
|
+
# Addition: add content from patch
|
|
251
|
+
content = h_line[1:]
|
|
252
|
+
if h_i + 1 < len(hunk_body) and hunk_body[h_i+1].startswith('\\ No newline'):
|
|
253
|
+
content = content.rstrip('\n')
|
|
254
|
+
patched_lines.append(content)
|
|
255
|
+
|
|
256
|
+
elif h_line.strip() == '':
|
|
257
|
+
# "Empty" line in patch. Legacy behavior: append to output.
|
|
258
|
+
# Does not consume original.
|
|
259
|
+
patched_lines.append(h_line)
|
|
260
|
+
|
|
261
|
+
elif h_line.startswith('\\'):
|
|
262
|
+
# No newline marker, handled by lookahead in + case.
|
|
263
|
+
# For - and space, we used original lines so their newline status is preserved automatically!
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
h_i += 1
|
|
267
|
+
|
|
268
|
+
orig_idx = found_idx + expected_count
|
|
269
|
+
|
|
270
|
+
patched_lines.extend(original_lines[orig_idx:])
|
|
271
|
+
return patched_lines
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import aiohttp
|
|
3
|
+
import base64
|
|
4
|
+
import shutil
|
|
5
|
+
import os
|
|
6
|
+
import ssl
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
SSL_VERIFY_ENV_VAR = "AUTOBYTEUS_DOWNLOAD_VERIFY_SSL"
|
|
12
|
+
SSL_CERT_FILE_ENV_VAR = "AUTOBYTEUS_DOWNLOAD_SSL_CERT_FILE"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _resolve_ssl_param() -> bool | ssl.SSLContext:
|
|
16
|
+
"""
|
|
17
|
+
Resolve SSL verification behavior for downloads.
|
|
18
|
+
|
|
19
|
+
- If AUTOBYTEUS_DOWNLOAD_SSL_CERT_FILE is set, use it as CA file.
|
|
20
|
+
- Else if AUTOBYTEUS_DOWNLOAD_VERIFY_SSL is truthy, verify using system CAs.
|
|
21
|
+
- Else (default), disable verification to allow self-signed certs.
|
|
22
|
+
"""
|
|
23
|
+
cert_path = os.getenv(SSL_CERT_FILE_ENV_VAR)
|
|
24
|
+
if cert_path:
|
|
25
|
+
cert_file = Path(cert_path)
|
|
26
|
+
if not cert_file.is_file():
|
|
27
|
+
raise IOError(f"SSL cert file not found: {cert_file}")
|
|
28
|
+
return ssl.create_default_context(cafile=str(cert_file))
|
|
29
|
+
|
|
30
|
+
verify_env = os.getenv(SSL_VERIFY_ENV_VAR)
|
|
31
|
+
if verify_env and verify_env.strip().lower() in {"1", "true", "yes", "on"}:
|
|
32
|
+
return True
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def download_file_from_url(url: str, file_path: Path) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Asynchronously downloads a file from a URL, decodes a data URI, or copies a local file
|
|
39
|
+
to a specified file path.
|
|
40
|
+
|
|
41
|
+
Features:
|
|
42
|
+
- Supports http/https URLs, data: URIs (base64), and local file paths.
|
|
43
|
+
- Creates parent directories if they don't exist.
|
|
44
|
+
- Uses streaming for HTTP URLs to handle large files efficiently.
|
|
45
|
+
- Guarantees cleanup of partial files on failure.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
url: The source URL, data URI, or local path.
|
|
49
|
+
file_path: The specific local path (including filename) to save to.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
IOError: If download, decoding, or copying fails.
|
|
53
|
+
"""
|
|
54
|
+
# Ensure parent directory exists
|
|
55
|
+
try:
|
|
56
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.error(f"Failed to create directory structure for {file_path}: {e}")
|
|
59
|
+
raise IOError(f"Filesystem error: {e}")
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
if url.startswith("data:"):
|
|
63
|
+
# Handle Data URI
|
|
64
|
+
try:
|
|
65
|
+
header, encoded = url.split(",", 1)
|
|
66
|
+
data = base64.b64decode(encoded)
|
|
67
|
+
with open(file_path, "wb") as f:
|
|
68
|
+
f.write(data)
|
|
69
|
+
logger.info(f"Successfully decoded and saved data URI to: {file_path}")
|
|
70
|
+
return
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"Failed to decode data URI: {e}")
|
|
73
|
+
raise IOError(f"Invalid data URI format: {e}")
|
|
74
|
+
|
|
75
|
+
if os.path.exists(url) and os.path.isfile(url):
|
|
76
|
+
# Handle Local File Path (Copy)
|
|
77
|
+
try:
|
|
78
|
+
shutil.copy(url, file_path)
|
|
79
|
+
logger.info(f"Successfully copied local file from {url} to {file_path}")
|
|
80
|
+
return
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.error(f"Failed to copy local file from {url} to {file_path}: {e}")
|
|
83
|
+
raise IOError(f"File copy error: {e}")
|
|
84
|
+
|
|
85
|
+
# Handle HTTP URL
|
|
86
|
+
ssl_param = _resolve_ssl_param()
|
|
87
|
+
async with aiohttp.ClientSession() as session:
|
|
88
|
+
async with session.get(url, ssl=ssl_param) as response:
|
|
89
|
+
if response.status != 200:
|
|
90
|
+
raise IOError(f"Failed to download from {url}: HTTP {response.status}")
|
|
91
|
+
|
|
92
|
+
# Open file for writing binary
|
|
93
|
+
with open(file_path, "wb") as f:
|
|
94
|
+
# Iterate over chunks to avoid loading large files into memory
|
|
95
|
+
async for chunk in response.content.iter_chunked(8192):
|
|
96
|
+
f.write(chunk)
|
|
97
|
+
|
|
98
|
+
logger.info(f"Successfully downloaded file to: {file_path}")
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.error(f"Failed to process media from {url} to {file_path}: {e}")
|
|
102
|
+
# Cleanup: Delete the partial/empty file if it was created
|
|
103
|
+
if file_path.exists():
|
|
104
|
+
try:
|
|
105
|
+
file_path.unlink()
|
|
106
|
+
logger.debug(f"Cleaned up partial file: {file_path}")
|
|
107
|
+
except OSError as cleanup_error:
|
|
108
|
+
logger.warning(f"Failed to clean up partial file {file_path}: {cleanup_error}")
|
|
109
|
+
raise
|