openhands-sdk 1.58.2__tar.gz → 1.59.0__tar.gz
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.
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/PKG-INFO +1 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/__init__.py +0 -14
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_agent.py +50 -2
- openhands_sdk-1.59.0/openhands/sdk/agent/acp_skills.py +67 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/agent.py +38 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/conversation.py +5 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/event_store.py +131 -52
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/impl/local_conversation.py +32 -9
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/impl/remote_conversation.py +32 -9
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/request.py +17 -4
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/secret_registry.py +10 -10
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/state.py +36 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/__init__.py +2 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/acp_tool_call.py +2 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/base.py +3 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/hook_execution.py +3 -3
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_completion_log.py +2 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/types.py +13 -3
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/fetch.py +18 -11
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/manager.py +3 -3
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/launch/finalize.py +4 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/launch/preview.py +1 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/launch/resolve.py +1 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/exceptions/classifier.py +31 -5
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/llm.py +9 -1
- openhands_sdk-1.59.0/openhands/sdk/llm/utils/retry_after.py +67 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/retry_mixin.py +23 -6
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/agent_profile.py +23 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/resolver.py +3 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/__init__.py +0 -6
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/acp_install_catalog.py +1 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/acp_providers.py +0 -209
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/skill.py +4 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/utils.py +11 -0
- openhands_sdk-1.59.0/openhands/sdk/utils/masking.py +70 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands_sdk.egg-info/PKG-INFO +1 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands_sdk.egg-info/SOURCES.txt +4 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/pyproject.toml +1 -1
- openhands_sdk-1.58.2/openhands/sdk/utils/masking.py +0 -13
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_contracts.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_file_credentials.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_model_discovery.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_tracing.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/response_dispatch.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/stream_context.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/automation/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/prompts/summarizing_events.j2 +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/prompts/summarizing_system.j2 +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/memory.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/presets.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/sections/static.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/message_request.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/credential.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/error_classification.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/security.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/git_changes.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/git_commits.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/launch/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/launch/errors.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/_message_normalization.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/_response_stream.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/_tokenizer.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/auth/openai.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/call_context.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/llm_profile_store.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/meta_profile_store.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/model_features.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/logger/_rich_handler.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/marketplace/registration.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/marketplace/registry.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/config.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/oauth.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/discovery.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/fetch.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/paths.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/strict_fetch.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/profile_refs.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/seed.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/api_models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/model.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/installed.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/subagent/load.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/subagent/registry.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/subagent/schema.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/subagent/scope.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/classify_and_switch_llm.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/defaults.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/files.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/redact.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/remote/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/repo.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands_sdk.egg-info/requires.txt +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.59.0
|
|
4
4
|
Summary: OpenHands SDK - Core functionality for building AI agents
|
|
5
5
|
Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
|
|
6
6
|
Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
-
from typing import TYPE_CHECKING, Any
|
|
5
4
|
|
|
6
5
|
from openhands.sdk.agent import (
|
|
7
6
|
Agent,
|
|
@@ -109,10 +108,6 @@ from openhands.sdk.workspace import (
|
|
|
109
108
|
)
|
|
110
109
|
|
|
111
110
|
|
|
112
|
-
if TYPE_CHECKING:
|
|
113
|
-
from openhands.sdk.settings.acp_providers import ACPModelOption
|
|
114
|
-
|
|
115
|
-
|
|
116
111
|
try:
|
|
117
112
|
__version__ = version("openhands-sdk")
|
|
118
113
|
except PackageNotFoundError:
|
|
@@ -167,7 +162,6 @@ __all__ = [
|
|
|
167
162
|
"ACP_PROVIDERS",
|
|
168
163
|
"ACPAgentSettings",
|
|
169
164
|
"ACPFileSecretSpec",
|
|
170
|
-
"ACPModelOption",
|
|
171
165
|
"ACPProviderInfo",
|
|
172
166
|
"AgentSettingsBase",
|
|
173
167
|
"AgentSettingsConfig",
|
|
@@ -211,11 +205,3 @@ __all__ = [
|
|
|
211
205
|
"page_iterator",
|
|
212
206
|
"__version__",
|
|
213
207
|
]
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def __getattr__(name: str) -> Any:
|
|
217
|
-
if name == "ACPModelOption":
|
|
218
|
-
from openhands.sdk.settings.acp_providers import _deprecated_model_option
|
|
219
|
-
|
|
220
|
-
return _deprecated_model_option()
|
|
221
|
-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -23,6 +23,7 @@ import inspect
|
|
|
23
23
|
import json
|
|
24
24
|
import os
|
|
25
25
|
import re
|
|
26
|
+
import shutil
|
|
26
27
|
import threading
|
|
27
28
|
import time
|
|
28
29
|
import uuid
|
|
@@ -106,6 +107,7 @@ from openhands.sdk.agent.acp_file_credentials import (
|
|
|
106
107
|
write_secret_file,
|
|
107
108
|
)
|
|
108
109
|
from openhands.sdk.agent.acp_models import ACPModelInfo
|
|
110
|
+
from openhands.sdk.agent.acp_skills import render_skill_catalog, write_skill_files
|
|
109
111
|
from openhands.sdk.agent.acp_tracing import ACPTurnTrace
|
|
110
112
|
from openhands.sdk.agent.base import AgentBase
|
|
111
113
|
from openhands.sdk.agent.stream_context import StreamContext
|
|
@@ -2020,6 +2022,10 @@ class ACPAgent(AgentBase):
|
|
|
2020
2022
|
# "installed" — already in subprocess history; skip further injection
|
|
2021
2023
|
_suffix_install_state: str = PrivateAttr(default="unused")
|
|
2022
2024
|
_installed_suffix: str | None = PrivateAttr(default=None)
|
|
2025
|
+
# Skills written to disk and listed by path in the prompt, not inlined.
|
|
2026
|
+
_skill_locations: dict[str, Path] = PrivateAttr(default_factory=dict)
|
|
2027
|
+
_skills_root: Path | None = PrivateAttr(default=None)
|
|
2028
|
+
_additional_directories_supported: bool = PrivateAttr(default=False)
|
|
2023
2029
|
_restart_session_on_next_turn: bool = PrivateAttr(default=False)
|
|
2024
2030
|
# Stream identity for the turn in flight; see stream_context.py. Held on
|
|
2025
2031
|
# the agent rather than threaded through the finalizers because the ACP
|
|
@@ -2331,6 +2337,7 @@ class ACPAgent(AgentBase):
|
|
|
2331
2337
|
|
|
2332
2338
|
# Render the suffix once, pulling secrets from the conversation's
|
|
2333
2339
|
# secret_registry to match the regular Agent's get_dynamic_context().
|
|
2340
|
+
self._materialize_skills(state)
|
|
2334
2341
|
self._installed_suffix = self._render_suffix(state)
|
|
2335
2342
|
# A prior session id means we may be resuming; used by ``truly_resumed``
|
|
2336
2343
|
# below to decide whether the model state reported for this launch
|
|
@@ -2493,6 +2500,25 @@ class ACPAgent(AgentBase):
|
|
|
2493
2500
|
)
|
|
2494
2501
|
)
|
|
2495
2502
|
|
|
2503
|
+
def _skill_directories(self) -> list[str] | None:
|
|
2504
|
+
if self._skills_root and self._additional_directories_supported:
|
|
2505
|
+
return [str(self._skills_root)]
|
|
2506
|
+
return None
|
|
2507
|
+
|
|
2508
|
+
def _materialize_skills(self, state: ConversationState) -> None:
|
|
2509
|
+
self._skill_locations = {}
|
|
2510
|
+
self._skills_root = None
|
|
2511
|
+
context = self.agent_context
|
|
2512
|
+
if context is None or not context.skills:
|
|
2513
|
+
return
|
|
2514
|
+
_, available = context._partition_skills()
|
|
2515
|
+
if not available:
|
|
2516
|
+
return
|
|
2517
|
+
root = self._acp_file_secret_dir(state, "skills")
|
|
2518
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
2519
|
+
self._skill_locations = write_skill_files(root, available)
|
|
2520
|
+
self._skills_root = root
|
|
2521
|
+
|
|
2496
2522
|
def _render_suffix(self, state: ConversationState) -> str | None:
|
|
2497
2523
|
"""Render the system suffix once, including secrets from the registry.
|
|
2498
2524
|
|
|
@@ -2533,12 +2559,23 @@ class ACPAgent(AgentBase):
|
|
|
2533
2559
|
if not secret_infos:
|
|
2534
2560
|
return None
|
|
2535
2561
|
agent_context = AgentContext(current_datetime=None)
|
|
2536
|
-
|
|
2562
|
+
listed = [s for s in agent_context.skills if s.name in self._skill_locations]
|
|
2563
|
+
if listed:
|
|
2564
|
+
agent_context = agent_context.model_copy(
|
|
2565
|
+
update={"skills": [s for s in agent_context.skills if s not in listed]}
|
|
2566
|
+
)
|
|
2567
|
+
if agent_context.secrets:
|
|
2537
2568
|
# The registry already carries these (and their descriptions), so
|
|
2538
2569
|
# clear the agent_context copy to advertise from the registry alone
|
|
2539
2570
|
# rather than re-merging a redundant second source.
|
|
2540
2571
|
agent_context = agent_context.model_copy(update={"secrets": {}})
|
|
2541
|
-
|
|
2572
|
+
rendered = agent_context.to_acp_prompt_context(
|
|
2573
|
+
additional_secret_infos=secret_infos
|
|
2574
|
+
)
|
|
2575
|
+
if not listed:
|
|
2576
|
+
return rendered
|
|
2577
|
+
catalog = render_skill_catalog(listed, self._skill_locations)
|
|
2578
|
+
return f"{rendered}\n\n{catalog}" if rendered else catalog
|
|
2542
2579
|
|
|
2543
2580
|
def _present_file_secret_names(self, state: ConversationState) -> set[str]:
|
|
2544
2581
|
"""Reserved file-content secret names supplied for this conversation.
|
|
@@ -3161,6 +3198,15 @@ class ACPAgent(AgentBase):
|
|
|
3161
3198
|
if init_response.agent_capabilities is not None
|
|
3162
3199
|
else None
|
|
3163
3200
|
)
|
|
3201
|
+
session_caps = (
|
|
3202
|
+
init_response.agent_capabilities.session_capabilities
|
|
3203
|
+
if init_response.agent_capabilities is not None
|
|
3204
|
+
else None
|
|
3205
|
+
)
|
|
3206
|
+
self._additional_directories_supported = (
|
|
3207
|
+
session_caps is not None
|
|
3208
|
+
and session_caps.additional_directories is not None
|
|
3209
|
+
)
|
|
3164
3210
|
acp_mcp_servers = _mcp_config_to_acp_servers(self.mcp_config, mcp_caps)
|
|
3165
3211
|
if acp_mcp_servers:
|
|
3166
3212
|
logger.info(
|
|
@@ -3256,6 +3302,7 @@ class ACPAgent(AgentBase):
|
|
|
3256
3302
|
load_response = await conn.load_session(
|
|
3257
3303
|
cwd=working_dir,
|
|
3258
3304
|
session_id=prior_session_id,
|
|
3305
|
+
additional_directories=self._skill_directories(),
|
|
3259
3306
|
mcp_servers=acp_mcp_servers,
|
|
3260
3307
|
)
|
|
3261
3308
|
session_id = prior_session_id
|
|
@@ -3301,6 +3348,7 @@ class ACPAgent(AgentBase):
|
|
|
3301
3348
|
session_meta = build_session_model_meta(agent_name, self.acp_model)
|
|
3302
3349
|
response = await conn.new_session(
|
|
3303
3350
|
cwd=working_dir,
|
|
3351
|
+
additional_directories=self._skill_directories(),
|
|
3304
3352
|
mcp_servers=acp_mcp_servers,
|
|
3305
3353
|
**session_meta,
|
|
3306
3354
|
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Expose skills to any ACP agent as files plus a catalog that points at them."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
from html import escape
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from openhands.sdk.skills import Skill
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _description(skill: Skill) -> str:
|
|
15
|
+
if skill.description:
|
|
16
|
+
return skill.description
|
|
17
|
+
return next(
|
|
18
|
+
(line.strip() for line in skill.content.splitlines() if line.strip()),
|
|
19
|
+
skill.name,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _directory_name(name: str, taken: set[str]) -> str:
|
|
24
|
+
base = re.sub(r"[^A-Za-z0-9._-]+", "-", name).strip(".-") or "skill"
|
|
25
|
+
candidate, suffix = base, 2
|
|
26
|
+
while candidate.lower() in taken:
|
|
27
|
+
candidate, suffix = f"{base}-{suffix}", suffix + 1
|
|
28
|
+
taken.add(candidate.lower())
|
|
29
|
+
return candidate
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def write_skill_files(root: Path, skills: Sequence[Skill]) -> dict[str, Path]:
|
|
33
|
+
"""Write each skill to ``root/<dir>/SKILL.md``; return its path by skill name."""
|
|
34
|
+
locations: dict[str, Path] = {}
|
|
35
|
+
taken: set[str] = set()
|
|
36
|
+
for skill in skills:
|
|
37
|
+
skill_dir = root / _directory_name(skill.name, taken)
|
|
38
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
path = skill_dir / "SKILL.md"
|
|
40
|
+
name = json.dumps(skill.name, ensure_ascii=False)
|
|
41
|
+
description = json.dumps(_description(skill)[:1000], ensure_ascii=False)
|
|
42
|
+
path.write_text(
|
|
43
|
+
f"---\nname: {name}\ndescription: {description}\n---\n\n{skill.content}\n",
|
|
44
|
+
encoding="utf-8",
|
|
45
|
+
)
|
|
46
|
+
locations[skill.name] = path
|
|
47
|
+
return locations
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def render_skill_catalog(skills: Sequence[Skill], locations: dict[str, Path]) -> str:
|
|
51
|
+
entries = [
|
|
52
|
+
" <skill>\n"
|
|
53
|
+
f" <name>{escape(skill.name)}</name>\n"
|
|
54
|
+
f" <description>{escape(_description(skill)[:1024])}</description>\n"
|
|
55
|
+
f" <location>{escape(str(locations[skill.name]))}</location>\n"
|
|
56
|
+
" </skill>"
|
|
57
|
+
for skill in skills
|
|
58
|
+
if skill.name in locations
|
|
59
|
+
]
|
|
60
|
+
return (
|
|
61
|
+
"<SKILLS>\n"
|
|
62
|
+
"The following skills are available. When one is relevant to the task, "
|
|
63
|
+
"read its SKILL.md at the given location with your file-reading tool "
|
|
64
|
+
"and follow its instructions.\n\n"
|
|
65
|
+
"<available_skills>\n" + "\n".join(entries) + "\n</available_skills>\n"
|
|
66
|
+
"</SKILLS>"
|
|
67
|
+
)
|
|
@@ -52,6 +52,7 @@ from openhands.sdk.event.condenser import (
|
|
|
52
52
|
)
|
|
53
53
|
from openhands.sdk.event.error_classification import AGENT_OUTCOME
|
|
54
54
|
from openhands.sdk.event.security import SecurityAnalysisEvent
|
|
55
|
+
from openhands.sdk.event.types import ROOT_PARENT_ID, EventID
|
|
55
56
|
from openhands.sdk.llm import (
|
|
56
57
|
LLM,
|
|
57
58
|
ImageContent,
|
|
@@ -561,12 +562,25 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
561
562
|
)
|
|
562
563
|
return
|
|
563
564
|
|
|
565
|
+
# A damaged event log can lose the SystemPromptEvent's file. The first
|
|
566
|
+
# surviving event still names it as its parent, or names the copy that an
|
|
567
|
+
# earlier resume re-created under the same id.
|
|
568
|
+
restored_prompt_id: EventID | None = None
|
|
569
|
+
if has_user_message:
|
|
570
|
+
parent_id = state.events[0].parent_id
|
|
571
|
+
if parent_id is not None and parent_id in state.events:
|
|
572
|
+
parent = state.events[state.events.get_index(parent_id)]
|
|
573
|
+
if isinstance(parent, SystemPromptEvent):
|
|
574
|
+
return
|
|
575
|
+
elif parent_id not in (None, ROOT_PARENT_ID):
|
|
576
|
+
restored_prompt_id = parent_id
|
|
577
|
+
|
|
564
578
|
# Assert: A user message should never appear before the system prompt.
|
|
565
579
|
#
|
|
566
580
|
# NOTE: This is a best-effort check based on the first few events only.
|
|
567
581
|
# Remote conversations can include a ConversationStateUpdateEvent near the
|
|
568
582
|
# start, so we scan a small prefix window.
|
|
569
|
-
if has_user_message:
|
|
583
|
+
if has_user_message and restored_prompt_id is None:
|
|
570
584
|
event_types = [type(e).__name__ for e in prefix_events]
|
|
571
585
|
logger.error(
|
|
572
586
|
f"init_state: User message found in prefix before SystemPromptEvent! "
|
|
@@ -598,7 +612,29 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
598
612
|
if dynamic_context
|
|
599
613
|
else None,
|
|
600
614
|
)
|
|
601
|
-
|
|
615
|
+
if restored_prompt_id is None:
|
|
616
|
+
on_event(event)
|
|
617
|
+
return
|
|
618
|
+
|
|
619
|
+
# Re-create it as an explicit root under its old id, so the surviving
|
|
620
|
+
# history hangs off it again; appending must not move HEAD onto it.
|
|
621
|
+
logger.warning(
|
|
622
|
+
"SystemPromptEvent '%s' is missing from the event log (its file was "
|
|
623
|
+
"lost); re-creating it as the root of the surviving history. "
|
|
624
|
+
"conversation_id=%s",
|
|
625
|
+
restored_prompt_id,
|
|
626
|
+
state.id,
|
|
627
|
+
)
|
|
628
|
+
head = state.active_branch(limit=1)
|
|
629
|
+
head_is_empty = state.head_is_empty
|
|
630
|
+
on_event(
|
|
631
|
+
event.model_copy(
|
|
632
|
+
update={"id": restored_prompt_id, "parent_id": ROOT_PARENT_ID}
|
|
633
|
+
)
|
|
634
|
+
)
|
|
635
|
+
state.leaf_event_id = head[-1].id if head else None
|
|
636
|
+
state.head_is_empty = head_is_empty
|
|
637
|
+
state.rebuild_view()
|
|
602
638
|
|
|
603
639
|
def get_dynamic_context(self, state: ConversationState) -> str | None:
|
|
604
640
|
"""Get dynamic context for the system prompt, including secrets from state.
|
|
@@ -73,6 +73,7 @@ class Conversation:
|
|
|
73
73
|
token_callbacks: list[ConversationTokenCallbackType] | None = None,
|
|
74
74
|
hook_config: HookConfig | None = None,
|
|
75
75
|
max_iteration_per_run: int = 500,
|
|
76
|
+
max_budget_per_run: float | None = None,
|
|
76
77
|
stuck_detection: bool = True,
|
|
77
78
|
stuck_detection_thresholds: (
|
|
78
79
|
StuckDetectionThresholds | Mapping[str, int] | None
|
|
@@ -103,6 +104,7 @@ class Conversation:
|
|
|
103
104
|
token_callbacks: list[ConversationTokenCallbackType] | None = None,
|
|
104
105
|
hook_config: HookConfig | None = None,
|
|
105
106
|
max_iteration_per_run: int = 500,
|
|
107
|
+
max_budget_per_run: float | None = None,
|
|
106
108
|
stuck_detection: bool = True,
|
|
107
109
|
stuck_detection_thresholds: (
|
|
108
110
|
StuckDetectionThresholds | Mapping[str, int] | None
|
|
@@ -133,6 +135,7 @@ class Conversation:
|
|
|
133
135
|
token_callbacks: list[ConversationTokenCallbackType] | None = None,
|
|
134
136
|
hook_config: HookConfig | None = None,
|
|
135
137
|
max_iteration_per_run: int = 500,
|
|
138
|
+
max_budget_per_run: float | None = None,
|
|
136
139
|
stuck_detection: bool = True,
|
|
137
140
|
stuck_detection_thresholds: (
|
|
138
141
|
StuckDetectionThresholds | Mapping[str, int] | None
|
|
@@ -200,6 +203,7 @@ class Conversation:
|
|
|
200
203
|
token_callbacks=token_callbacks,
|
|
201
204
|
hook_config=hook_config,
|
|
202
205
|
max_iteration_per_run=max_iteration_per_run,
|
|
206
|
+
max_budget_per_run=max_budget_per_run,
|
|
203
207
|
stuck_detection=stuck_detection,
|
|
204
208
|
stuck_detection_thresholds=stuck_detection_thresholds,
|
|
205
209
|
visualizer=visualizer,
|
|
@@ -223,6 +227,7 @@ class Conversation:
|
|
|
223
227
|
token_callbacks=token_callbacks,
|
|
224
228
|
hook_config=hook_config,
|
|
225
229
|
max_iteration_per_run=max_iteration_per_run,
|
|
230
|
+
max_budget_per_run=max_budget_per_run,
|
|
226
231
|
stuck_detection=stuck_detection,
|
|
227
232
|
stuck_detection_thresholds=stuck_detection_thresholds,
|
|
228
233
|
visualizer=visualizer,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# state.py
|
|
2
|
+
import bisect
|
|
2
3
|
import operator
|
|
3
4
|
from collections.abc import Callable, Iterator
|
|
4
5
|
from contextlib import AbstractContextManager, nullcontext
|
|
@@ -26,6 +27,9 @@ LOCK_TIMEOUT_SECONDS = 30
|
|
|
26
27
|
# ``exists()``. The count lives in the name because ``FileStore.read`` is cached.
|
|
27
28
|
LENGTH_MARKER_PATTERN: Final[str] = ".eventlog-len-{length}.marker"
|
|
28
29
|
|
|
30
|
+
# Holes listed by name in the lost-files warning; the rest are only counted.
|
|
31
|
+
MAX_REPORTED_HOLES: Final[int] = 5
|
|
32
|
+
|
|
29
33
|
# ROOT_PARENT_ID now lives in event.types (single source of truth); it is used
|
|
30
34
|
# below in _effective_parent_id and re-exported here so existing
|
|
31
35
|
# ``from event_store import ROOT_PARENT_ID`` importers keep working.
|
|
@@ -55,6 +59,13 @@ class EventLog(EventsListBase):
|
|
|
55
59
|
self._dir = dir_path
|
|
56
60
|
self._id_to_idx: dict[EventID, int] = {}
|
|
57
61
|
self._idx_to_id: dict[int, EventID] = {}
|
|
62
|
+
# On-disk index per log index, recorded only where the two differ (behind
|
|
63
|
+
# a lost event file). The on-disk index names the file and is the
|
|
64
|
+
# event's durable seq; new files are allocated after the highest one.
|
|
65
|
+
self._disk_idx: dict[int, int] = {}
|
|
66
|
+
self._next_disk_idx = 0
|
|
67
|
+
self._reported_lost = 0
|
|
68
|
+
self._reported_missing: set[EventID] = set()
|
|
58
69
|
self._event_cache: dict[int, Event] = {}
|
|
59
70
|
self._lock_path = f"{dir_path}/{LOCK_FILE_NAME}"
|
|
60
71
|
self._write_guard = None
|
|
@@ -81,6 +92,28 @@ class EventLog(EventsListBase):
|
|
|
81
92
|
raise IndexError("Event index out of range")
|
|
82
93
|
return self._idx_to_id[idx]
|
|
83
94
|
|
|
95
|
+
def get_seq(self, idx: int) -> int:
|
|
96
|
+
"""Return the durable sequence number of the event at ``idx``.
|
|
97
|
+
|
|
98
|
+
The seq is the event's on-disk index (the ``{idx:05d}`` filename
|
|
99
|
+
component). It equals ``idx`` unless an earlier event file was lost, and
|
|
100
|
+
unlike ``idx`` it never changes across a reload, so it is the value to
|
|
101
|
+
hand out as a resume cursor.
|
|
102
|
+
"""
|
|
103
|
+
if idx < 0:
|
|
104
|
+
idx += self._length
|
|
105
|
+
if idx < 0 or idx >= self._length:
|
|
106
|
+
raise IndexError("Event index out of range")
|
|
107
|
+
return self._disk_idx.get(idx, idx)
|
|
108
|
+
|
|
109
|
+
def index_after_seq(self, seq: int) -> int:
|
|
110
|
+
"""Return the index of the first event whose seq is above ``seq``.
|
|
111
|
+
|
|
112
|
+
Returns ``len(self)`` when there is none. Seqs grow with the index, so
|
|
113
|
+
this is a binary search.
|
|
114
|
+
"""
|
|
115
|
+
return bisect.bisect_right(range(self._length), seq, key=self.get_seq)
|
|
116
|
+
|
|
84
117
|
def __contains__(self, item: object) -> bool:
|
|
85
118
|
"""Whether the log contains a given event id or ``Event`` (by id).
|
|
86
119
|
|
|
@@ -97,15 +130,30 @@ class EventLog(EventsListBase):
|
|
|
97
130
|
|
|
98
131
|
Legacy events predating the tree have no ``parent_id``; they fall back to
|
|
99
132
|
the linear chain (event ``idx - 1``) so old conversations load unbranched
|
|
100
|
-
with no disk rewrite.
|
|
133
|
+
with no disk rewrite. An explicit parent whose file was lost is bridged
|
|
134
|
+
the same way, so the branch keeps its surviving ancestors (the system
|
|
135
|
+
prompt above all) instead of ending at the hole.
|
|
101
136
|
"""
|
|
102
|
-
|
|
137
|
+
parent_id = event.parent_id
|
|
138
|
+
if parent_id == ROOT_PARENT_ID:
|
|
103
139
|
return None # explicit root (feature-created root at idx > 0)
|
|
104
|
-
if
|
|
105
|
-
|
|
140
|
+
if parent_id is not None:
|
|
141
|
+
if parent_id in self._id_to_idx:
|
|
142
|
+
return parent_id # explicit (new events)
|
|
143
|
+
self._report_missing(parent_id)
|
|
106
144
|
if idx == 0:
|
|
107
145
|
return None # genuine root
|
|
108
|
-
return self.get_id(idx - 1) # legacy linear chain
|
|
146
|
+
return self.get_id(idx - 1) # legacy linear chain, or bridge over a hole
|
|
147
|
+
|
|
148
|
+
def _report_missing(self, event_id: EventID) -> None:
|
|
149
|
+
"""Warn once per id that the log references an event whose file is gone."""
|
|
150
|
+
if event_id in self._reported_missing:
|
|
151
|
+
return
|
|
152
|
+
self._reported_missing.add(event_id)
|
|
153
|
+
logger.warning(
|
|
154
|
+
"Event '%s' is missing from the event log (its file was lost).",
|
|
155
|
+
event_id,
|
|
156
|
+
)
|
|
109
157
|
|
|
110
158
|
def path_to_root(
|
|
111
159
|
self, leaf_id: EventID | None, limit: int | None = None
|
|
@@ -114,8 +162,9 @@ class EventLog(EventsListBase):
|
|
|
114
162
|
|
|
115
163
|
``leaf_id=None`` yields ``[]``. ``limit`` keeps only the last ``limit``
|
|
116
164
|
events (walking back from the leaf), so callers wanting a recent window
|
|
117
|
-
stay O(limit) instead of O(branch). Raises ValueError on a cycle
|
|
118
|
-
|
|
165
|
+
stay O(limit) instead of O(branch). Raises ValueError on a cycle. An
|
|
166
|
+
ancestor whose file was lost is bridged (see ``_effective_parent_id``);
|
|
167
|
+
a ``leaf_id`` missing from the log yields ``[]``.
|
|
119
168
|
"""
|
|
120
169
|
chain: list[Event] = []
|
|
121
170
|
seen: set[EventID] = set()
|
|
@@ -124,7 +173,11 @@ class EventLog(EventsListBase):
|
|
|
124
173
|
if cur_id in seen:
|
|
125
174
|
raise ValueError(f"Cycle in event tree at {cur_id}")
|
|
126
175
|
seen.add(cur_id)
|
|
127
|
-
idx = self.
|
|
176
|
+
idx = self._id_to_idx.get(cur_id)
|
|
177
|
+
if idx is None:
|
|
178
|
+
# Only the leaf can be unknown: ancestors are bridged.
|
|
179
|
+
self._report_missing(cur_id)
|
|
180
|
+
break
|
|
128
181
|
chain.append(evt := self[idx])
|
|
129
182
|
cur_id = self._effective_parent_id(idx, evt)
|
|
130
183
|
return chain[::-1]
|
|
@@ -227,9 +280,15 @@ class EventLog(EventsListBase):
|
|
|
227
280
|
nullcontext() if self._write_guard is None else self._write_guard()
|
|
228
281
|
)
|
|
229
282
|
idx = self._length
|
|
283
|
+
# Never reuse an on-disk index: behind a lost file the dense
|
|
284
|
+
# index lags it, and a shared index makes the reload order
|
|
285
|
+
# depend on the event ids.
|
|
286
|
+
disk_idx = max(idx, self._next_disk_idx)
|
|
230
287
|
with write_guard:
|
|
231
|
-
|
|
232
|
-
|
|
288
|
+
self._fs.write(self._file_path(disk_idx, evt_id), payload)
|
|
289
|
+
if disk_idx != idx:
|
|
290
|
+
self._disk_idx[idx] = disk_idx
|
|
291
|
+
self._next_disk_idx = disk_idx + 1
|
|
233
292
|
self._idx_to_id[idx] = evt_id
|
|
234
293
|
self._id_to_idx[evt_id] = idx
|
|
235
294
|
self._event_cache[idx] = event
|
|
@@ -292,17 +351,25 @@ class EventLog(EventsListBase):
|
|
|
292
351
|
Preserves existing index mappings and only scans new events.
|
|
293
352
|
"""
|
|
294
353
|
# Preserve existing mappings
|
|
295
|
-
|
|
354
|
+
existing = [
|
|
355
|
+
(idx, evt_id, self._disk_idx.get(idx, idx))
|
|
356
|
+
for idx, evt_id in self._idx_to_id.items()
|
|
357
|
+
]
|
|
296
358
|
|
|
297
359
|
# Re-scan to pick up new events
|
|
298
360
|
scanned_length = self._scan_and_build_index()
|
|
299
361
|
|
|
300
|
-
# Restore
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if evt_id
|
|
305
|
-
|
|
362
|
+
# Restore mappings the scan cannot see (e.g., for non-UUID event IDs).
|
|
363
|
+
# Only whole pairs, and only when neither side was rescanned: behind a
|
|
364
|
+
# lost file a rescanned index can belong to a different event.
|
|
365
|
+
for idx, evt_id, disk_idx in existing:
|
|
366
|
+
if idx in self._idx_to_id or evt_id in self._id_to_idx:
|
|
367
|
+
continue
|
|
368
|
+
self._idx_to_id[idx] = evt_id
|
|
369
|
+
self._id_to_idx[evt_id] = idx
|
|
370
|
+
if disk_idx != idx:
|
|
371
|
+
self._disk_idx[idx] = disk_idx
|
|
372
|
+
self._next_disk_idx = max(self._next_disk_idx, disk_idx + 1)
|
|
306
373
|
|
|
307
374
|
# Use the higher of scanned length or disk_length
|
|
308
375
|
self._length = max(scanned_length, disk_length)
|
|
@@ -311,58 +378,57 @@ class EventLog(EventsListBase):
|
|
|
311
378
|
return self._length
|
|
312
379
|
|
|
313
380
|
def _path(self, idx: int, *, event_id: EventID | None = None) -> str:
|
|
314
|
-
return
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
381
|
+
return self._file_path(
|
|
382
|
+
self._disk_idx.get(idx, idx), event_id or self._idx_to_id[idx]
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
def _file_path(self, disk_idx: int, event_id: EventID) -> str:
|
|
386
|
+
name = EVENT_FILE_PATTERN.format(idx=disk_idx, event_id=event_id)
|
|
387
|
+
return f"{self._dir}/{name}"
|
|
388
|
+
|
|
389
|
+
def _reset_index(self) -> None:
|
|
390
|
+
self._id_to_idx.clear()
|
|
391
|
+
self._idx_to_id.clear()
|
|
392
|
+
self._disk_idx.clear()
|
|
393
|
+
self._event_cache.clear()
|
|
394
|
+
self._next_disk_idx = 0
|
|
319
395
|
|
|
320
396
|
def _scan_and_build_index(self) -> int:
|
|
321
397
|
try:
|
|
322
398
|
paths = self._fs.list(self._dir)
|
|
323
399
|
except Exception:
|
|
324
|
-
self.
|
|
325
|
-
self._idx_to_id.clear()
|
|
326
|
-
self._event_cache.clear()
|
|
400
|
+
self._reset_index()
|
|
327
401
|
return 0
|
|
328
402
|
|
|
329
|
-
|
|
403
|
+
found: list[tuple[int, EventID]] = []
|
|
330
404
|
for p in paths:
|
|
331
405
|
name = posix_path_name(p)
|
|
332
406
|
m = EVENT_NAME_RE.match(name)
|
|
333
407
|
if m:
|
|
334
|
-
|
|
335
|
-
evt_id = m.group("event_id")
|
|
336
|
-
by_idx[idx] = evt_id
|
|
408
|
+
found.append((int(m.group("idx")), m.group("event_id")))
|
|
337
409
|
elif name.startswith("."):
|
|
338
410
|
# Our own sidecars (lock file, count marker), not event files.
|
|
339
411
|
continue
|
|
340
412
|
else:
|
|
341
413
|
logger.warning(f"Unrecognized event file name: {name}")
|
|
342
414
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
break
|
|
358
|
-
n += 1
|
|
359
|
-
|
|
360
|
-
self._id_to_idx.clear()
|
|
361
|
-
self._idx_to_id.clear()
|
|
362
|
-
self._event_cache.clear()
|
|
363
|
-
for i in range(n):
|
|
364
|
-
evt_id = by_idx[i]
|
|
415
|
+
self._reset_index()
|
|
416
|
+
# Index every event file in on-disk order instead of stopping at the
|
|
417
|
+
# first missing index, so one lost file does not make every later event
|
|
418
|
+
# unknown. The dense index closes the hole; the on-disk index stays the
|
|
419
|
+
# event's file name and seq.
|
|
420
|
+
found.sort()
|
|
421
|
+
holes: list[tuple[int, int]] = []
|
|
422
|
+
lost = 0
|
|
423
|
+
for i, (disk_idx, evt_id) in enumerate(found):
|
|
424
|
+
if disk_idx > self._next_disk_idx:
|
|
425
|
+
lost += disk_idx - self._next_disk_idx
|
|
426
|
+
if len(holes) < MAX_REPORTED_HOLES:
|
|
427
|
+
holes.append((self._next_disk_idx, disk_idx - 1))
|
|
428
|
+
self._next_disk_idx = disk_idx + 1
|
|
365
429
|
self._idx_to_id[i] = evt_id
|
|
430
|
+
if disk_idx != i:
|
|
431
|
+
self._disk_idx[i] = disk_idx
|
|
366
432
|
if evt_id in self._id_to_idx:
|
|
367
433
|
logger.warning(
|
|
368
434
|
f"Duplicate event ID '{evt_id}' found during scan. "
|
|
@@ -371,4 +437,17 @@ class EventLog(EventsListBase):
|
|
|
371
437
|
)
|
|
372
438
|
else:
|
|
373
439
|
self._id_to_idx[evt_id] = i
|
|
374
|
-
|
|
440
|
+
if lost != self._reported_lost:
|
|
441
|
+
self._reported_lost = lost
|
|
442
|
+
if lost:
|
|
443
|
+
listed = ", ".join(str(a) if a == b else f"{a}-{b}" for a, b in holes)
|
|
444
|
+
logger.warning(
|
|
445
|
+
"Event log %s is missing %d event file(s) (on-disk index %s%s); "
|
|
446
|
+
"the %d surviving events stay readable.",
|
|
447
|
+
self._dir,
|
|
448
|
+
lost,
|
|
449
|
+
listed,
|
|
450
|
+
", ..." if lost > sum(b - a + 1 for a, b in holes) else "",
|
|
451
|
+
len(found),
|
|
452
|
+
)
|
|
453
|
+
return len(found)
|