autobyteus 1.2.1__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 +19 -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 +69 -59
- 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/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 -183
- 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 -223
- autobyteus/agent/streaming/stream_events.py +3 -140
- 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 +1 -2
- autobyteus/agent_team/bootstrap_steps/team_context_initialization_step.py +4 -4
- 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 +9 -6
- 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 +6 -6
- autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py +4 -4
- autobyteus/agent_team/streaming/agent_team_stream_events.py +3 -3
- 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/task_notification_mode.py +19 -0
- autobyteus/agent_team/utils/wait_for_idle.py +4 -4
- autobyteus/cli/agent_cli.py +18 -10
- autobyteus/cli/agent_team_tui/app.py +14 -11
- autobyteus/cli/agent_team_tui/state.py +13 -15
- autobyteus/cli/agent_team_tui/widgets/agent_list_sidebar.py +15 -15
- autobyteus/cli/agent_team_tui/widgets/focus_pane.py +143 -36
- autobyteus/cli/agent_team_tui/widgets/renderables.py +1 -1
- autobyteus/cli/agent_team_tui/widgets/shared.py +25 -25
- 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 +11 -18
- 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/autobyteus_audio_client.py +19 -5
- autobyteus/multimedia/audio/api/gemini_audio_client.py +108 -16
- autobyteus/multimedia/audio/audio_client_factory.py +47 -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/tools/todo_tools/add_todo.py +2 -2
- autobyteus/task_management/tools/todo_tools/create_todo_list.py +2 -2
- autobyteus/task_management/tools/todo_tools/update_todo_status.py +2 -2
- autobyteus/tools/__init__.py +34 -47
- autobyteus/tools/base_tool.py +7 -0
- autobyteus/tools/file/__init__.py +2 -6
- autobyteus/tools/file/patch_file.py +149 -0
- autobyteus/tools/file/read_file.py +36 -5
- autobyteus/tools/file/write_file.py +4 -1
- autobyteus/tools/functional_tool.py +43 -6
- 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/types.py +61 -0
- autobyteus/tools/multimedia/audio_tools.py +70 -17
- autobyteus/tools/multimedia/download_media_tool.py +18 -4
- autobyteus/tools/multimedia/image_tools.py +246 -62
- 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 +43 -2
- 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/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 +3 -9
- 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.1.dist-info → autobyteus-1.2.3.dist-info}/WHEEL +1 -1
- {autobyteus-1.2.1.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/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/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/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 -84
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +0 -101
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +0 -169
- autobyteus/tools/browser/standalone/webpage_reader.py +0 -105
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +0 -105
- autobyteus/tools/file/edit_file.py +0 -200
- autobyteus/tools/file/list_directory.py +0 -168
- autobyteus/tools/file/search_files.py +0 -188
- autobyteus/tools/timer.py +0 -175
- 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.1.dist-info/METADATA +0 -205
- autobyteus-1.2.1.dist-info/RECORD +0 -511
- examples/__init__.py +0 -1
- examples/agent_team/__init__.py +0 -1
- examples/discover_phase_transitions.py +0 -104
- examples/run_agentic_software_engineer.py +0 -239
- 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/{tools/browser/session_aware → skills}/__init__.py +0 -0
- /autobyteus/tools/{browser/session_aware/factory → skill}/__init__.py +0 -0
- {autobyteus-1.2.1.dist-info → autobyteus-1.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""
|
|
2
|
+
XmlWriteFileToolParsingState: Streams <tool name="write_file"> blocks.
|
|
3
|
+
|
|
4
|
+
This state specializes the generic XmlToolParsingState to stream file content
|
|
5
|
+
and capture the path for display. Argument parsing is handled later by the
|
|
6
|
+
ToolInvocationAdapter.
|
|
7
|
+
"""
|
|
8
|
+
from typing import TYPE_CHECKING, Optional
|
|
9
|
+
|
|
10
|
+
from .xml_tool_parsing_state import XmlToolParsingState
|
|
11
|
+
from ..events import SegmentType
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ..parser_context import ParserContext
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class XmlWriteFileToolParsingState(XmlToolParsingState):
|
|
18
|
+
"""
|
|
19
|
+
Streams <tool name="write_file"> tool calls.
|
|
20
|
+
|
|
21
|
+
This state operates identically to XmlToolParsingState but provides
|
|
22
|
+
a distinct type (WRITE_FILE) and specialized metadata handling if needed.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
SEGMENT_TYPE = SegmentType.WRITE_FILE
|
|
26
|
+
START_CONTENT_MARKER = "__START_CONTENT__"
|
|
27
|
+
END_CONTENT_MARKER = "__END_CONTENT__"
|
|
28
|
+
CONTENT_ARG_CLOSE_TAG = "</arg>"
|
|
29
|
+
|
|
30
|
+
def __init__(self, context: "ParserContext", opening_tag: str):
|
|
31
|
+
super().__init__(context, opening_tag)
|
|
32
|
+
if self._tool_name != "write_file":
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
# Internal state for streaming
|
|
36
|
+
self._found_content_start = False
|
|
37
|
+
self._content_buffering = ""
|
|
38
|
+
self._captured_path: Optional[str] = None
|
|
39
|
+
self._defer_start = True # New flag to defer emission
|
|
40
|
+
self._swallowing_remaining = False # New flag to swallow closing tags
|
|
41
|
+
self._content_mode = "seek_marker"
|
|
42
|
+
self._content_seek_buffer = ""
|
|
43
|
+
self._marker_tail = ""
|
|
44
|
+
|
|
45
|
+
def run(self) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Custom run loop to stream ONLY the content argument.
|
|
48
|
+
"""
|
|
49
|
+
from .text_state import TextState
|
|
50
|
+
|
|
51
|
+
if self._swallowing_remaining:
|
|
52
|
+
self._handle_swallowing()
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
# Note: We do NOT emit start immediately anymore.
|
|
56
|
+
|
|
57
|
+
if not self.context.has_more_chars():
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
chunk = self.context.consume_remaining()
|
|
61
|
+
|
|
62
|
+
if not self._found_content_start:
|
|
63
|
+
self._content_buffering += chunk
|
|
64
|
+
|
|
65
|
+
import re
|
|
66
|
+
|
|
67
|
+
# 1. Try to find path if missing
|
|
68
|
+
if not self._captured_path:
|
|
69
|
+
path_match = re.search(r'<arg\s+name=["\']path["\']>([^<]+)</arg>', self._content_buffering, re.IGNORECASE)
|
|
70
|
+
if path_match:
|
|
71
|
+
self._captured_path = path_match.group(1).strip()
|
|
72
|
+
# Now we have path, we can emit start if we were waiting for it
|
|
73
|
+
if self._defer_start and not self._segment_started:
|
|
74
|
+
# Construct metadata with path
|
|
75
|
+
meta = self._get_start_metadata()
|
|
76
|
+
meta["path"] = self._captured_path
|
|
77
|
+
self.context.emit_segment_start(self.SEGMENT_TYPE, **meta)
|
|
78
|
+
self._segment_started = True
|
|
79
|
+
self._defer_start = False
|
|
80
|
+
|
|
81
|
+
# 2. Look for content start
|
|
82
|
+
match = re.search(r'<arg\s+name=["\']content["\']>', self._content_buffering, re.IGNORECASE)
|
|
83
|
+
|
|
84
|
+
if match:
|
|
85
|
+
self._found_content_start = True
|
|
86
|
+
end_of_tag = match.end()
|
|
87
|
+
|
|
88
|
+
# If we still haven't emitted start (e.g. no path found but content started), emit now without path
|
|
89
|
+
if not self._segment_started:
|
|
90
|
+
self.context.emit_segment_start(self.SEGMENT_TYPE, **self._get_start_metadata())
|
|
91
|
+
self._segment_started = True
|
|
92
|
+
|
|
93
|
+
# Update path in metadata if we found it late (redundant but safe)
|
|
94
|
+
if self._captured_path:
|
|
95
|
+
self.context.update_current_segment_metadata(path=self._captured_path)
|
|
96
|
+
|
|
97
|
+
real_content = self._content_buffering[end_of_tag:]
|
|
98
|
+
self._content_buffering = ""
|
|
99
|
+
self._content_mode = "seek_marker"
|
|
100
|
+
self._content_seek_buffer = ""
|
|
101
|
+
self._marker_tail = ""
|
|
102
|
+
self._tail = ""
|
|
103
|
+
self._process_content_chunk(real_content)
|
|
104
|
+
else:
|
|
105
|
+
# If closing tool and still no content
|
|
106
|
+
if "</tool>" in self._content_buffering:
|
|
107
|
+
# If start never happened, force it
|
|
108
|
+
if not self._segment_started:
|
|
109
|
+
self.context.emit_segment_start(self.SEGMENT_TYPE, **self._get_start_metadata())
|
|
110
|
+
self._segment_started = True
|
|
111
|
+
|
|
112
|
+
self._on_segment_complete()
|
|
113
|
+
self.context.emit_segment_end()
|
|
114
|
+
self.context.transition_to(TextState(self.context))
|
|
115
|
+
else:
|
|
116
|
+
self._process_content_chunk(chunk)
|
|
117
|
+
|
|
118
|
+
def _process_content_chunk(self, chunk: str) -> None:
|
|
119
|
+
"""Process content chunk, supporting optional content markers."""
|
|
120
|
+
if not chunk:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
if self._content_mode == "marker":
|
|
124
|
+
self._process_marker_content(chunk)
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
if self._content_mode == "default":
|
|
128
|
+
self._process_default_content(chunk)
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
self._process_seek_marker_content(chunk)
|
|
132
|
+
|
|
133
|
+
def _process_seek_marker_content(self, chunk: str) -> None:
|
|
134
|
+
"""Seek __START_CONTENT__ before committing to default parsing."""
|
|
135
|
+
self._content_seek_buffer += chunk
|
|
136
|
+
|
|
137
|
+
start_idx = self._content_seek_buffer.find(self.START_CONTENT_MARKER)
|
|
138
|
+
if start_idx != -1:
|
|
139
|
+
after_start = self._content_seek_buffer[start_idx + len(self.START_CONTENT_MARKER):]
|
|
140
|
+
# Strip leading newline after marker to avoid empty first line
|
|
141
|
+
if after_start.startswith("\n"):
|
|
142
|
+
after_start = after_start[1:]
|
|
143
|
+
self._content_seek_buffer = ""
|
|
144
|
+
self._content_mode = "marker"
|
|
145
|
+
self._marker_tail = ""
|
|
146
|
+
self._tail = ""
|
|
147
|
+
if after_start:
|
|
148
|
+
self._process_marker_content(after_start)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
closing_idx = self._content_seek_buffer.find(self.CONTENT_ARG_CLOSE_TAG)
|
|
152
|
+
if closing_idx != -1:
|
|
153
|
+
buffered = self._content_seek_buffer
|
|
154
|
+
self._content_seek_buffer = ""
|
|
155
|
+
self._content_mode = "default"
|
|
156
|
+
self._tail = ""
|
|
157
|
+
self._process_default_content(buffered)
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
stripped = self._content_seek_buffer.lstrip()
|
|
161
|
+
if stripped and not self.START_CONTENT_MARKER.startswith(stripped):
|
|
162
|
+
buffered = self._content_seek_buffer
|
|
163
|
+
self._content_seek_buffer = ""
|
|
164
|
+
self._content_mode = "default"
|
|
165
|
+
self._tail = ""
|
|
166
|
+
self._process_default_content(buffered)
|
|
167
|
+
|
|
168
|
+
def _process_default_content(self, chunk: str) -> None:
|
|
169
|
+
"""Process content chunk, stripping closing tags."""
|
|
170
|
+
closing_tag = self.CONTENT_ARG_CLOSE_TAG
|
|
171
|
+
combined = self._tail + chunk
|
|
172
|
+
|
|
173
|
+
idx = combined.find(closing_tag)
|
|
174
|
+
|
|
175
|
+
if idx != -1:
|
|
176
|
+
actual_content = combined[:idx]
|
|
177
|
+
if actual_content:
|
|
178
|
+
self.context.emit_segment_content(actual_content)
|
|
179
|
+
|
|
180
|
+
# We found the end of the content argument.
|
|
181
|
+
# Instead of stopping, we switch to swallowing mode to eat </arguments></tool>
|
|
182
|
+
self._tail = ""
|
|
183
|
+
remainder = combined[idx + len(closing_tag):]
|
|
184
|
+
self._content_buffering = remainder
|
|
185
|
+
self._swallowing_remaining = True
|
|
186
|
+
|
|
187
|
+
# Immediately try to finish if we have the closing tags
|
|
188
|
+
self._handle_swallowing()
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
holdback_len = len(closing_tag) - 1
|
|
192
|
+
if len(combined) > holdback_len:
|
|
193
|
+
safe = combined[:-holdback_len]
|
|
194
|
+
if safe:
|
|
195
|
+
self.context.emit_segment_content(safe)
|
|
196
|
+
self._tail = combined[-holdback_len:]
|
|
197
|
+
else:
|
|
198
|
+
self._tail = combined
|
|
199
|
+
|
|
200
|
+
def _process_marker_content(self, chunk: str) -> None:
|
|
201
|
+
"""Process content chunk when inside __START_CONTENT__/__END_CONTENT__ markers.
|
|
202
|
+
|
|
203
|
+
The __END_CONTENT__ sentinel is only valid if followed by optional whitespace
|
|
204
|
+
and then </arg>. This prevents false positives when file content contains
|
|
205
|
+
the literal __END_CONTENT__ string.
|
|
206
|
+
"""
|
|
207
|
+
import re
|
|
208
|
+
|
|
209
|
+
combined = self._marker_tail + chunk
|
|
210
|
+
end_marker = self.END_CONTENT_MARKER
|
|
211
|
+
closing_tag = self.CONTENT_ARG_CLOSE_TAG
|
|
212
|
+
|
|
213
|
+
# Priority 1: Check for the explicit end marker WITH lookahead validation
|
|
214
|
+
# We need to find __END_CONTENT__ that is followed by whitespace* + </arg>
|
|
215
|
+
search_start = 0
|
|
216
|
+
while True:
|
|
217
|
+
idx = combined.find(end_marker, search_start)
|
|
218
|
+
if idx == -1:
|
|
219
|
+
break
|
|
220
|
+
|
|
221
|
+
remainder_after_marker = combined[idx + len(end_marker):]
|
|
222
|
+
|
|
223
|
+
# Validate: must be followed by whitespace* + </arg>
|
|
224
|
+
if re.match(r'^\s*</arg>', remainder_after_marker):
|
|
225
|
+
# Valid sentinel - emit content and transition
|
|
226
|
+
actual_content = combined[:idx]
|
|
227
|
+
if actual_content:
|
|
228
|
+
self.context.emit_segment_content(actual_content)
|
|
229
|
+
|
|
230
|
+
self._marker_tail = ""
|
|
231
|
+
remainder = combined[idx + len(end_marker):]
|
|
232
|
+
self._content_buffering = remainder
|
|
233
|
+
self._swallowing_remaining = True
|
|
234
|
+
self._handle_swallowing()
|
|
235
|
+
return
|
|
236
|
+
elif remainder_after_marker.strip() == "":
|
|
237
|
+
# Indeterminate - need more data to decide if this is valid
|
|
238
|
+
# Hold back from idx onwards
|
|
239
|
+
if idx > 0:
|
|
240
|
+
# Emit safe content before the potential marker
|
|
241
|
+
safe_content = combined[:idx]
|
|
242
|
+
if safe_content:
|
|
243
|
+
self.context.emit_segment_content(safe_content)
|
|
244
|
+
self._marker_tail = combined[idx:]
|
|
245
|
+
else:
|
|
246
|
+
self._marker_tail = combined
|
|
247
|
+
return
|
|
248
|
+
else:
|
|
249
|
+
# False positive - this __END_CONTENT__ is part of the file content
|
|
250
|
+
# Continue searching for the next occurrence
|
|
251
|
+
search_start = idx + len(end_marker)
|
|
252
|
+
|
|
253
|
+
# Priority 2: Check for closing arg tag as fallback (missing sentinel case)
|
|
254
|
+
# Only treat </arg> as terminator if it looks like the actual XML structure end
|
|
255
|
+
# (i.e. followed by </arguments> or </tool>)
|
|
256
|
+
idx_close = combined.find(closing_tag)
|
|
257
|
+
if idx_close != -1:
|
|
258
|
+
remainder_after_close = combined[idx_close + len(closing_tag):]
|
|
259
|
+
|
|
260
|
+
# Check if followed by standard XML closure (ignoring whitespace)
|
|
261
|
+
# We match if we see the start of the next tag, OR if we have only whitespace (ambiguous - wait)
|
|
262
|
+
|
|
263
|
+
is_valid_closure = False
|
|
264
|
+
# If we see the next tag immediately start
|
|
265
|
+
if re.match(r'^\s*(?:</arguments>|</tool>)', remainder_after_close):
|
|
266
|
+
is_valid_closure = True
|
|
267
|
+
|
|
268
|
+
# If we have indeterminate whitespace, we must hold back to be sure
|
|
269
|
+
elif remainder_after_close.strip() == "":
|
|
270
|
+
# We can't decide yet. Hold back everything from existing tag start.
|
|
271
|
+
# But we can allow partial emit of previous content if we separate it.
|
|
272
|
+
# Let's just hold back the whole combined tail to be safe/simple.
|
|
273
|
+
self._marker_tail = combined
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
if is_valid_closure:
|
|
277
|
+
actual_content = combined[:idx_close]
|
|
278
|
+
|
|
279
|
+
# User request: remove the last \n for the file content
|
|
280
|
+
# This specifically handles the indented </arg> case logic
|
|
281
|
+
if re.search(r'\n\s*$', actual_content):
|
|
282
|
+
actual_content = re.sub(r'\n\s*$', '', actual_content)
|
|
283
|
+
|
|
284
|
+
if actual_content:
|
|
285
|
+
self.context.emit_segment_content(actual_content)
|
|
286
|
+
|
|
287
|
+
self._marker_tail = ""
|
|
288
|
+
remainder = combined[idx_close + len(closing_tag):]
|
|
289
|
+
self._content_buffering = remainder
|
|
290
|
+
self._swallowing_remaining = True
|
|
291
|
+
self._handle_swallowing()
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
# Holdback logic
|
|
295
|
+
# We need to hold back enough to detect EITHER marker OR closing_tag + context
|
|
296
|
+
# </arg> (6) + \s + </tool> (7) ~= 15-20 chars holdback
|
|
297
|
+
# But to be safe against splitting </arguments>, let's hold back ~20 chars.
|
|
298
|
+
# Also need to account for __END_CONTENT__ (16 chars) + whitespace + </arg> (6) = ~25 chars
|
|
299
|
+
|
|
300
|
+
max_holdback = 35 # Safe buffer for regex lookahead
|
|
301
|
+
|
|
302
|
+
if len(combined) > max_holdback:
|
|
303
|
+
safe = combined[:-max_holdback]
|
|
304
|
+
if safe:
|
|
305
|
+
self.context.emit_segment_content(safe)
|
|
306
|
+
self._marker_tail = combined[-max_holdback:]
|
|
307
|
+
else:
|
|
308
|
+
self._marker_tail = combined
|
|
309
|
+
|
|
310
|
+
def _handle_swallowing(self) -> None:
|
|
311
|
+
"""Consume stream until </tool> is found."""
|
|
312
|
+
from .text_state import TextState
|
|
313
|
+
|
|
314
|
+
# Add any new data to buffer
|
|
315
|
+
self._content_buffering += self.context.consume_remaining()
|
|
316
|
+
|
|
317
|
+
closing_tag = "</tool>"
|
|
318
|
+
idx = self._content_buffering.find(closing_tag)
|
|
319
|
+
|
|
320
|
+
if idx != -1:
|
|
321
|
+
# We found the end!
|
|
322
|
+
# We are done with this tool.
|
|
323
|
+
|
|
324
|
+
# Anything after </tool> belongs to the next state (TextState)
|
|
325
|
+
remainder = self._content_buffering[idx + len(closing_tag):]
|
|
326
|
+
|
|
327
|
+
self._on_segment_complete()
|
|
328
|
+
self.context.emit_segment_end()
|
|
329
|
+
if remainder:
|
|
330
|
+
# Rewind so the next state can parse the remainder (e.g., another tool tag).
|
|
331
|
+
self.context.rewind_by(len(remainder))
|
|
332
|
+
self.context.transition_to(TextState(self.context))
|
|
333
|
+
else:
|
|
334
|
+
# Nothing yet, keep swallowing (clearing buffer to avoid memory issues if valid)
|
|
335
|
+
# But we need to keep a holdback in case </tool> is split?
|
|
336
|
+
# </tool> is 7 chars.
|
|
337
|
+
holdback_len = len(closing_tag) - 1
|
|
338
|
+
if len(self._content_buffering) > holdback_len:
|
|
339
|
+
# Discard safe prefix
|
|
340
|
+
self._content_buffering = self._content_buffering[-holdback_len:]
|
|
341
|
+
|
|
342
|
+
def _on_segment_complete(self) -> None:
|
|
343
|
+
return None
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Detection strategies for streaming parser.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .base import DetectionStrategy
|
|
6
|
+
from .json_tool_strategy import JsonToolStrategy
|
|
7
|
+
from .sentinel_strategy import SentinelStrategy
|
|
8
|
+
from .xml_tag_strategy import XmlTagStrategy
|
|
9
|
+
from .registry import create_detection_strategies
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DetectionStrategy",
|
|
13
|
+
"JsonToolStrategy",
|
|
14
|
+
"SentinelStrategy",
|
|
15
|
+
"XmlTagStrategy",
|
|
16
|
+
"create_detection_strategies",
|
|
17
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Detection strategy interfaces for streaming parser.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Protocol, TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..parser_context import ParserContext
|
|
10
|
+
from ..states.base_state import BaseState
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DetectionStrategy(Protocol):
|
|
14
|
+
"""Protocol for detection strategies."""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
|
|
18
|
+
def next_marker(self, context: "ParserContext", start_pos: int) -> int:
|
|
19
|
+
"""Return next marker index or -1 if not applicable."""
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def create_state(self, context: "ParserContext") -> "BaseState":
|
|
23
|
+
"""Create the state to handle parsing after this marker."""
|
|
24
|
+
...
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Detection strategy for JSON tool calls.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..parser_context import ParserContext
|
|
10
|
+
from ..states.base_state import BaseState
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class JsonToolStrategy:
|
|
14
|
+
name = "json_tool"
|
|
15
|
+
|
|
16
|
+
def next_marker(self, context: "ParserContext", start_pos: int) -> int:
|
|
17
|
+
if not context.parse_tool_calls:
|
|
18
|
+
return -1
|
|
19
|
+
next_curly = context.find("{", start_pos)
|
|
20
|
+
next_bracket = context.find("[", start_pos)
|
|
21
|
+
candidates = [idx for idx in (next_curly, next_bracket) if idx != -1]
|
|
22
|
+
return min(candidates) if candidates else -1
|
|
23
|
+
|
|
24
|
+
def create_state(self, context: "ParserContext") -> "BaseState":
|
|
25
|
+
from ..states.json_initialization_state import JsonInitializationState
|
|
26
|
+
return JsonInitializationState(context)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Registry for detection strategies.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Dict, List
|
|
7
|
+
|
|
8
|
+
from .base import DetectionStrategy
|
|
9
|
+
from .json_tool_strategy import JsonToolStrategy
|
|
10
|
+
from .sentinel_strategy import SentinelStrategy
|
|
11
|
+
from .xml_tag_strategy import XmlTagStrategy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_STRATEGY_REGISTRY: Dict[str, DetectionStrategy] = {
|
|
15
|
+
"sentinel": SentinelStrategy(),
|
|
16
|
+
"xml_tag": XmlTagStrategy(),
|
|
17
|
+
"json_tool": JsonToolStrategy(),
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_detection_strategies(strategy_order: List[str]) -> List[DetectionStrategy]:
|
|
22
|
+
"""Create ordered detection strategies."""
|
|
23
|
+
strategies: List[DetectionStrategy] = []
|
|
24
|
+
for name in strategy_order:
|
|
25
|
+
strategy = _STRATEGY_REGISTRY.get(name)
|
|
26
|
+
if strategy:
|
|
27
|
+
strategies.append(strategy)
|
|
28
|
+
return strategies
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Detection strategy for sentinel-formatted segments.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from ..sentinel_format import START_MARKER
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..parser_context import ParserContext
|
|
12
|
+
from ..states.base_state import BaseState
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SentinelStrategy:
|
|
16
|
+
name = "sentinel"
|
|
17
|
+
|
|
18
|
+
def next_marker(self, context: "ParserContext", start_pos: int) -> int:
|
|
19
|
+
return context.find(START_MARKER, start_pos)
|
|
20
|
+
|
|
21
|
+
def create_state(self, context: "ParserContext") -> "BaseState":
|
|
22
|
+
from ..states.sentinel_initialization_state import SentinelInitializationState
|
|
23
|
+
return SentinelInitializationState(context)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Detection strategy for XML-like tags such as <file>, <bash>, <tool>, <!doctype>.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..parser_context import ParserContext
|
|
10
|
+
from ..states.base_state import BaseState
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class XmlTagStrategy:
|
|
14
|
+
name = "xml_tag"
|
|
15
|
+
|
|
16
|
+
def next_marker(self, context: "ParserContext", start_pos: int) -> int:
|
|
17
|
+
return context.find("<", start_pos)
|
|
18
|
+
|
|
19
|
+
def create_state(self, context: "ParserContext") -> "BaseState":
|
|
20
|
+
from ..states.xml_tag_initialization_state import XmlTagInitializationState
|
|
21
|
+
return XmlTagInitializationState(context)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
StreamScanner: A class to manage a string buffer and cursor position for sequential reading.
|
|
3
|
+
|
|
4
|
+
This encapsulates the logic of navigating a stream of text, preventing direct
|
|
5
|
+
manipulation of the cursor and buffer from multiple state classes.
|
|
6
|
+
"""
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StreamScanner:
|
|
11
|
+
"""
|
|
12
|
+
Manages a string buffer and a cursor position for sequential reading.
|
|
13
|
+
|
|
14
|
+
This class encapsulates navigation logic for streaming text, providing
|
|
15
|
+
a clean interface for state machine states to read characters without
|
|
16
|
+
directly manipulating the buffer.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, initial_buffer: str = ""):
|
|
20
|
+
"""Initialize the scanner with an optional initial buffer."""
|
|
21
|
+
self._buffer: str = initial_buffer
|
|
22
|
+
self._pos: int = 0
|
|
23
|
+
|
|
24
|
+
def append(self, text: str) -> None:
|
|
25
|
+
"""Append more text to the end of the buffer."""
|
|
26
|
+
self._buffer += text
|
|
27
|
+
|
|
28
|
+
def peek(self) -> Optional[str]:
|
|
29
|
+
"""
|
|
30
|
+
Look at the character at the current cursor position without advancing.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The character at the cursor, or None if at the end.
|
|
34
|
+
"""
|
|
35
|
+
if self._pos < len(self._buffer):
|
|
36
|
+
return self._buffer[self._pos]
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def advance(self) -> None:
|
|
40
|
+
"""Move the cursor forward by one position."""
|
|
41
|
+
if self.has_more_chars():
|
|
42
|
+
self._pos += 1
|
|
43
|
+
|
|
44
|
+
def advance_by(self, count: int) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Move the cursor forward by a specified number of positions.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
count: The number of characters to advance.
|
|
50
|
+
"""
|
|
51
|
+
self._pos = min(len(self._buffer), self._pos + count)
|
|
52
|
+
|
|
53
|
+
def has_more_chars(self) -> bool:
|
|
54
|
+
"""
|
|
55
|
+
Check if there are more characters to read from the buffer.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if the cursor is not at the end of the buffer.
|
|
59
|
+
"""
|
|
60
|
+
return self._pos < len(self._buffer)
|
|
61
|
+
|
|
62
|
+
def substring(self, start: int, end: Optional[int] = None) -> str:
|
|
63
|
+
"""
|
|
64
|
+
Extract a substring from the buffer.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
start: The starting index.
|
|
68
|
+
end: The ending index (exclusive). If None, reads to end of buffer.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The extracted substring.
|
|
72
|
+
"""
|
|
73
|
+
if end is None:
|
|
74
|
+
return self._buffer[start:]
|
|
75
|
+
return self._buffer[start:end]
|
|
76
|
+
|
|
77
|
+
def find(self, sub: str, start: Optional[int] = None) -> int:
|
|
78
|
+
"""
|
|
79
|
+
Find a substring in the buffer starting from a position.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
sub: The substring to search for.
|
|
83
|
+
start: Optional start index. Defaults to current position.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
The index of the substring, or -1 if not found.
|
|
87
|
+
"""
|
|
88
|
+
if start is None:
|
|
89
|
+
start = self._pos
|
|
90
|
+
return self._buffer.find(sub, start)
|
|
91
|
+
|
|
92
|
+
def consume(self, count: int) -> str:
|
|
93
|
+
"""
|
|
94
|
+
Consume a number of characters from the current position.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
count: Number of characters to consume.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
The consumed substring.
|
|
101
|
+
"""
|
|
102
|
+
if count <= 0:
|
|
103
|
+
return ""
|
|
104
|
+
end = min(len(self._buffer), self._pos + count)
|
|
105
|
+
result = self._buffer[self._pos:end]
|
|
106
|
+
self._pos = end
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
def consume_remaining(self) -> str:
|
|
110
|
+
"""
|
|
111
|
+
Consume all remaining characters in the buffer.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
The remaining substring from current position to end.
|
|
115
|
+
"""
|
|
116
|
+
if self._pos >= len(self._buffer):
|
|
117
|
+
return ""
|
|
118
|
+
result = self._buffer[self._pos:]
|
|
119
|
+
self._pos = len(self._buffer)
|
|
120
|
+
return result
|
|
121
|
+
|
|
122
|
+
def get_position(self) -> int:
|
|
123
|
+
"""
|
|
124
|
+
Return the current zero-based position of the cursor.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
The current cursor position.
|
|
128
|
+
"""
|
|
129
|
+
return self._pos
|
|
130
|
+
|
|
131
|
+
def get_buffer_length(self) -> int:
|
|
132
|
+
"""
|
|
133
|
+
Return the total length of the internal buffer.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
The length of the buffer.
|
|
137
|
+
"""
|
|
138
|
+
return len(self._buffer)
|
|
139
|
+
|
|
140
|
+
def set_position(self, position: int) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Set the cursor to a specific position.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
position: The new cursor position (clamped to valid range).
|
|
146
|
+
"""
|
|
147
|
+
self._pos = max(0, min(len(self._buffer), position))
|
|
148
|
+
|
|
149
|
+
def compact(self, min_prefix: int = 65536) -> None:
|
|
150
|
+
"""
|
|
151
|
+
Compact the buffer by dropping consumed prefix data.
|
|
152
|
+
|
|
153
|
+
If all data is consumed, clears the buffer entirely.
|
|
154
|
+
Otherwise, drops the consumed prefix when it exceeds min_prefix.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
min_prefix: Minimum consumed prefix length to trigger compaction.
|
|
158
|
+
"""
|
|
159
|
+
if self._pos == 0:
|
|
160
|
+
return
|
|
161
|
+
if self._pos >= len(self._buffer):
|
|
162
|
+
self._buffer = ""
|
|
163
|
+
self._pos = 0
|
|
164
|
+
return
|
|
165
|
+
if self._pos >= min_prefix:
|
|
166
|
+
self._buffer = self._buffer[self._pos:]
|
|
167
|
+
self._pos = 0
|