openhands-sdk 1.55.0__tar.gz → 1.55.1__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.55.0 → openhands_sdk-1.55.1}/PKG-INFO +1 -1
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/git/git_changes.py +22 -3
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/__init__.py +8 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/fetch.py +34 -1
- openhands_sdk-1.55.1/openhands/sdk/plugin/paths.py +17 -0
- openhands_sdk-1.55.1/openhands/sdk/plugin/strict_fetch.py +504 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/profiles/__init__.py +4 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/profiles/agent_profile.py +18 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands_sdk.egg-info/PKG-INFO +1 -1
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands_sdk.egg-info/SOURCES.txt +4 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/pyproject.toml +1 -1
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/acp_agent.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/acp_contracts.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/acp_file_credentials.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/acp_model_discovery.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/acp_tracing.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/agent.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/response_dispatch.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/stream_context.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/agent/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/automation/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/condenser/prompts/summarizing_events.j2 +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/condenser/prompts/summarizing_system.j2 +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/memory.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/prompts/presets.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/prompts/sections/planning.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/prompts/sections/static.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/conversation.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/impl/local_conversation.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/impl/remote_conversation.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/message_request.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/request.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/secret_registry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/state.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/credential.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/acp_tool_call.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/error_classification.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/hook_execution.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/security.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/extensions/fetch.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/extensions/installation/manager.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/git/git_commits.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/launch/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/launch/errors.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/launch/finalize.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/launch/preview.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/launch/resolve.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/_message_normalization.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/_response_stream.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/_tokenizer.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/auth/openai.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/call_context.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/cleanup_profile.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/exceptions/classifier.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/llm.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/llm_profile_store.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/meta_profile_store.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/provider_connection_store.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/model_features.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/verified_models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/marketplace/registration.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/marketplace/registry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/mcp/config.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/mcp/oauth.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/discovery.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/format/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/format/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/format/claude_code.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/profiles/agent_profile_store.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/profiles/profile_refs.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/profiles/resolver.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/profiles/seed.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/grayswan/analyzer.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/toolshield_helpers.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/settings/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/settings/acp_install_catalog.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/settings/acp_providers.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/settings/api_models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/settings/model.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/skills/installed.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/skills/skill.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/skills/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/subagent/load.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/subagent/registry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/subagent/schema.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/subagent/scope.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/builtins/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/builtins/classify_and_switch_llm.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/defaults.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/files.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/masking.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/redact.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/workspace/remote/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/workspace/repo.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands_sdk.egg-info/requires.txt +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.55.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.55.
|
|
3
|
+
Version: 1.55.1
|
|
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
|
|
@@ -9,7 +9,11 @@ import logging
|
|
|
9
9
|
import os
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
from openhands.sdk.git.exceptions import
|
|
12
|
+
from openhands.sdk.git.exceptions import (
|
|
13
|
+
GitCommandError,
|
|
14
|
+
GitError,
|
|
15
|
+
GitRepositoryError,
|
|
16
|
+
)
|
|
13
17
|
from openhands.sdk.git.models import GitChange, GitChangeStatus
|
|
14
18
|
from openhands.sdk.git.utils import (
|
|
15
19
|
get_valid_ref,
|
|
@@ -221,8 +225,17 @@ def get_git_changes(cwd: str | Path, ref: str | None = None) -> list[GitChange]:
|
|
|
221
225
|
for f in glob.glob("./*/.git", root_dir=cwd, recursive=True)
|
|
222
226
|
}
|
|
223
227
|
|
|
224
|
-
# First try the workspace directory
|
|
225
|
-
|
|
228
|
+
# First try the workspace directory. A directory that is not a repository
|
|
229
|
+
# itself but holds repositories below it (the ``{base}/{repo}`` layout of a
|
|
230
|
+
# repository-backed conversation) contributes no changes of its own.
|
|
231
|
+
base_error: GitRepositoryError | None = None
|
|
232
|
+
try:
|
|
233
|
+
changes = get_changes_in_repo(cwd, ref=ref)
|
|
234
|
+
except GitRepositoryError as e:
|
|
235
|
+
if not git_dirs:
|
|
236
|
+
raise
|
|
237
|
+
base_error = e
|
|
238
|
+
changes = []
|
|
226
239
|
|
|
227
240
|
# Filter out any changes which are inside one of the nested repositories.
|
|
228
241
|
# This compares path ancestry rather than string prefixes: a nested
|
|
@@ -236,6 +249,7 @@ def get_git_changes(cwd: str | Path, ref: str | None = None) -> list[GitChange]:
|
|
|
236
249
|
]
|
|
237
250
|
|
|
238
251
|
# Add changes from git directories
|
|
252
|
+
found_nested_repo = False
|
|
239
253
|
for git_dir in git_dirs:
|
|
240
254
|
try:
|
|
241
255
|
git_dir_changes = get_changes_in_repo(str(Path(cwd, git_dir)), ref=ref)
|
|
@@ -244,6 +258,7 @@ def get_git_changes(cwd: str | Path, ref: str | None = None) -> list[GitChange]:
|
|
|
244
258
|
f"Skipping nested git directory {git_dir}: not a valid repository"
|
|
245
259
|
)
|
|
246
260
|
continue
|
|
261
|
+
found_nested_repo = True
|
|
247
262
|
for change in git_dir_changes:
|
|
248
263
|
# Create a new GitChange with the updated path
|
|
249
264
|
updated_change = GitChange(
|
|
@@ -252,6 +267,10 @@ def get_git_changes(cwd: str | Path, ref: str | None = None) -> list[GitChange]:
|
|
|
252
267
|
)
|
|
253
268
|
changes.append(updated_change)
|
|
254
269
|
|
|
270
|
+
# Neither the directory nor anything below it is a repository.
|
|
271
|
+
if base_error is not None and not found_nested_repo:
|
|
272
|
+
raise base_error
|
|
273
|
+
|
|
255
274
|
changes.sort(key=lambda change: str(change.path))
|
|
256
275
|
|
|
257
276
|
return changes
|
|
@@ -49,6 +49,11 @@ from openhands.sdk.plugin.source import (
|
|
|
49
49
|
resolve_source_path,
|
|
50
50
|
validate_source_path,
|
|
51
51
|
)
|
|
52
|
+
from openhands.sdk.plugin.strict_fetch import (
|
|
53
|
+
CheckedPlugin,
|
|
54
|
+
PluginSourceError,
|
|
55
|
+
preflight_plugin,
|
|
56
|
+
)
|
|
52
57
|
from openhands.sdk.plugin.types import (
|
|
53
58
|
CommandDefinition,
|
|
54
59
|
PluginAuthor,
|
|
@@ -62,6 +67,8 @@ __all__ = [
|
|
|
62
67
|
# Plugin classes
|
|
63
68
|
"Plugin",
|
|
64
69
|
"PluginFetchError",
|
|
70
|
+
"PluginSourceError",
|
|
71
|
+
"CheckedPlugin",
|
|
65
72
|
"PluginManifest",
|
|
66
73
|
"PluginAuthor",
|
|
67
74
|
"PluginSource",
|
|
@@ -75,6 +82,7 @@ __all__ = [
|
|
|
75
82
|
# Plugin loading
|
|
76
83
|
"load_plugins",
|
|
77
84
|
"fetch_plugin_with_resolution",
|
|
85
|
+
"preflight_plugin",
|
|
78
86
|
# Local plugin discovery (ambient auto-load)
|
|
79
87
|
"load_user_plugins",
|
|
80
88
|
"load_project_plugins",
|
|
@@ -7,23 +7,50 @@ public interface.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import re
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
12
13
|
from openhands.sdk.extensions.fetch import (
|
|
13
14
|
ExtensionFetchError,
|
|
15
|
+
SourceType,
|
|
14
16
|
fetch_with_resolution as _ext_fetch_with_resolution,
|
|
17
|
+
parse_extension_source,
|
|
15
18
|
)
|
|
16
19
|
from openhands.sdk.git.cached_repo import GitHelper
|
|
20
|
+
from openhands.sdk.plugin.paths import get_pinned_plugin_cache_path
|
|
17
21
|
from openhands.sdk.utils.path import get_user_persistence_dir
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
DEFAULT_CACHE_DIR = get_user_persistence_dir() / "cache" / "plugins"
|
|
25
|
+
_FULL_SHA_RE = re.compile(r"^[0-9a-fA-F]{40}$")
|
|
21
26
|
|
|
22
27
|
|
|
23
28
|
class PluginFetchError(Exception):
|
|
24
29
|
"""Raised when fetching a plugin fails."""
|
|
25
30
|
|
|
26
31
|
|
|
32
|
+
def _cached_pinned_plugin(
|
|
33
|
+
source: str, cache_dir: Path, ref: str, repo_path: str | None
|
|
34
|
+
) -> Path | None:
|
|
35
|
+
try:
|
|
36
|
+
source_type, url = parse_extension_source(source)
|
|
37
|
+
except ExtensionFetchError:
|
|
38
|
+
return None
|
|
39
|
+
if source_type == SourceType.LOCAL:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
cached_root = get_pinned_plugin_cache_path(url, cache_dir, ref.lower())
|
|
43
|
+
if not cached_root.is_dir():
|
|
44
|
+
return None
|
|
45
|
+
if repo_path is None:
|
|
46
|
+
return cached_root
|
|
47
|
+
|
|
48
|
+
candidate = cached_root / repo_path.strip("/")
|
|
49
|
+
if candidate.is_dir() and candidate.resolve().is_relative_to(cached_root.resolve()):
|
|
50
|
+
return candidate
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
27
54
|
def fetch_plugin(
|
|
28
55
|
source: str,
|
|
29
56
|
cache_dir: Path | None = None,
|
|
@@ -98,6 +125,11 @@ def fetch_plugin_with_resolution(
|
|
|
98
125
|
PluginFetchError: If fetching fails or repo_path doesn't exist.
|
|
99
126
|
"""
|
|
100
127
|
resolved_cache_dir = cache_dir if cache_dir is not None else DEFAULT_CACHE_DIR
|
|
128
|
+
if ref is not None and _FULL_SHA_RE.fullmatch(ref):
|
|
129
|
+
cached = _cached_pinned_plugin(source, resolved_cache_dir, ref, repo_path)
|
|
130
|
+
if cached is not None:
|
|
131
|
+
return cached, ref.lower()
|
|
132
|
+
|
|
101
133
|
try:
|
|
102
134
|
return _ext_fetch_with_resolution(
|
|
103
135
|
source=source,
|
|
@@ -108,5 +140,6 @@ def fetch_plugin_with_resolution(
|
|
|
108
140
|
git_helper=git_helper,
|
|
109
141
|
)
|
|
110
142
|
except ExtensionFetchError as exc:
|
|
111
|
-
msg =
|
|
143
|
+
msg = re.sub(r"\bextension\b", "plugin", str(exc))
|
|
144
|
+
msg = re.sub(r"\bExtension\b", "Plugin", msg)
|
|
112
145
|
raise PluginFetchError(msg) from exc
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Filesystem paths shared by plugin fetch implementations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from openhands.sdk.extensions.fetch import get_cache_path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
PINNED_REFS_DIR = "resolved-refs"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_pinned_plugin_cache_path(
|
|
14
|
+
source_url: str, cache_dir: Path, resolved_ref: str
|
|
15
|
+
) -> Path:
|
|
16
|
+
"""Return the immutable cache path for a plugin source at a commit SHA."""
|
|
17
|
+
return get_cache_path(source_url, cache_dir) / PINNED_REFS_DIR / resolved_ref
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""Strict plugin source resolution for pre-flight checks and sandbox fetches."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
from collections.abc import Iterator, Mapping
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from filelock import FileLock
|
|
17
|
+
from pydantic import BaseModel, Field
|
|
18
|
+
|
|
19
|
+
from openhands.sdk.extensions.fetch import (
|
|
20
|
+
ExtensionFetchError,
|
|
21
|
+
SourceType,
|
|
22
|
+
parse_extension_source,
|
|
23
|
+
)
|
|
24
|
+
from openhands.sdk.plugin.fetch import DEFAULT_CACHE_DIR, PluginFetchError
|
|
25
|
+
from openhands.sdk.plugin.paths import get_pinned_plugin_cache_path
|
|
26
|
+
from openhands.sdk.plugin.plugin import Plugin
|
|
27
|
+
from openhands.sdk.secret import SecretSource, SecretValue
|
|
28
|
+
from openhands.sdk.skills.utils import expand_variable_references
|
|
29
|
+
from openhands.sdk.utils.path import resolves_within
|
|
30
|
+
from openhands.sdk.utils.redact import (
|
|
31
|
+
redact_url_credentials,
|
|
32
|
+
redact_url_credentials_in_text,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_FULL_SHA_RE = re.compile(r"^[0-9a-fA-F]{40}$")
|
|
37
|
+
_BRACED_VAR_RE = re.compile(r"\$\{([a-zA-Z_][a-zA-Z0-9_]*)(?::-([^}]*))?\}")
|
|
38
|
+
|
|
39
|
+
_ERROR_STATUSES = {
|
|
40
|
+
"missing_secret": 422,
|
|
41
|
+
"invalid_source": 422,
|
|
42
|
+
"source_not_allowed": 422,
|
|
43
|
+
"auth_failed": 422,
|
|
44
|
+
"source_unavailable": 502,
|
|
45
|
+
"ref_not_found": 422,
|
|
46
|
+
"repo_path_not_found": 422,
|
|
47
|
+
"invalid_plugin": 422,
|
|
48
|
+
"timeout": 504,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class PluginSourceError(PluginFetchError):
|
|
53
|
+
"""Raised when strict plugin source resolution fails."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, code: str, message: str):
|
|
56
|
+
super().__init__(message)
|
|
57
|
+
self.code = code
|
|
58
|
+
self.message = message
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def status_code(self) -> int:
|
|
62
|
+
return _ERROR_STATUSES.get(self.code, 422)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class CheckedPlugin(BaseModel):
|
|
66
|
+
"""A strictly resolved plugin checkout and its public metadata."""
|
|
67
|
+
|
|
68
|
+
path: Path = Field(description="Path to the resolved plugin directory")
|
|
69
|
+
resolved_ref: str | None = Field(description="Resolved git commit SHA")
|
|
70
|
+
source: str = Field(description="Original source, with placeholders preserved")
|
|
71
|
+
repo_path: str | None = Field(description="Subdirectory path within the repository")
|
|
72
|
+
manifest: dict[str, Any] = Field(description="Raw plugin.json contents")
|
|
73
|
+
skills: list[str] = Field(default_factory=list)
|
|
74
|
+
commands: list[str] = Field(default_factory=list)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def plugin_source_error_status(code: str) -> int:
|
|
78
|
+
"""Return the HTTP status code used for a strict plugin source error."""
|
|
79
|
+
return _ERROR_STATUSES.get(code, 422)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@contextmanager
|
|
83
|
+
def preflight_plugin(
|
|
84
|
+
source: str,
|
|
85
|
+
ref: str | None = None,
|
|
86
|
+
repo_path: str | None = None,
|
|
87
|
+
secrets: Mapping[str, SecretValue] | None = None,
|
|
88
|
+
*,
|
|
89
|
+
timeout: int = 60,
|
|
90
|
+
allow_local: bool = False,
|
|
91
|
+
) -> Iterator[CheckedPlugin]:
|
|
92
|
+
"""Strictly check a plugin source in a throwaway checkout.
|
|
93
|
+
|
|
94
|
+
The temporary checkout is deleted when the context exits, including when the
|
|
95
|
+
caller raises while inspecting files referenced by the manifest.
|
|
96
|
+
"""
|
|
97
|
+
with tempfile.TemporaryDirectory(prefix="openhands-plugin-preflight-") as tmp:
|
|
98
|
+
yield fetch_plugin_strict(
|
|
99
|
+
source=source,
|
|
100
|
+
ref=ref,
|
|
101
|
+
repo_path=repo_path,
|
|
102
|
+
secrets=secrets,
|
|
103
|
+
cache_dir=None,
|
|
104
|
+
work_dir=Path(tmp),
|
|
105
|
+
timeout=timeout,
|
|
106
|
+
allow_local=allow_local,
|
|
107
|
+
use_cache=False,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def fetch_plugin_strict(
|
|
112
|
+
*,
|
|
113
|
+
source: str,
|
|
114
|
+
ref: str | None = None,
|
|
115
|
+
repo_path: str | None = None,
|
|
116
|
+
secrets: Mapping[str, SecretValue] | None = None,
|
|
117
|
+
cache_dir: Path | None = None,
|
|
118
|
+
work_dir: Path | None = None,
|
|
119
|
+
timeout: int = 60,
|
|
120
|
+
allow_local: bool = False,
|
|
121
|
+
use_cache: bool = True,
|
|
122
|
+
) -> CheckedPlugin:
|
|
123
|
+
"""Strictly fetch and describe a plugin without installing it."""
|
|
124
|
+
resolved_cache_dir = (
|
|
125
|
+
DEFAULT_CACHE_DIR if use_cache and cache_dir is None else cache_dir
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
expanded_source, expanded_ref, secret_values = _expand_source_and_ref(
|
|
129
|
+
source, ref, secrets or {}
|
|
130
|
+
)
|
|
131
|
+
try:
|
|
132
|
+
source_type, url = parse_extension_source(expanded_source)
|
|
133
|
+
except ExtensionFetchError:
|
|
134
|
+
raise PluginSourceError(
|
|
135
|
+
"invalid_source",
|
|
136
|
+
"Invalid plugin source.",
|
|
137
|
+
) from None
|
|
138
|
+
|
|
139
|
+
if source_type == SourceType.LOCAL:
|
|
140
|
+
if not allow_local:
|
|
141
|
+
raise PluginSourceError(
|
|
142
|
+
"source_not_allowed",
|
|
143
|
+
"Local plugin sources are not allowed for this operation.",
|
|
144
|
+
)
|
|
145
|
+
plugin_root = _copy_local_source(url, repo_path, work_dir)
|
|
146
|
+
return _checked_plugin(
|
|
147
|
+
plugin_root=plugin_root,
|
|
148
|
+
source=source,
|
|
149
|
+
repo_path=repo_path,
|
|
150
|
+
resolved_ref=None,
|
|
151
|
+
secret_values=secret_values,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if expanded_source.startswith("file://") and not allow_local:
|
|
155
|
+
raise PluginSourceError(
|
|
156
|
+
"source_not_allowed",
|
|
157
|
+
"file:// plugin sources are not allowed for this operation.",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
resolved_ref = expanded_ref
|
|
161
|
+
if (
|
|
162
|
+
use_cache
|
|
163
|
+
and resolved_cache_dir is not None
|
|
164
|
+
and resolved_ref is not None
|
|
165
|
+
and _FULL_SHA_RE.fullmatch(resolved_ref)
|
|
166
|
+
):
|
|
167
|
+
cached_root = get_pinned_plugin_cache_path(
|
|
168
|
+
url, resolved_cache_dir, resolved_ref.lower()
|
|
169
|
+
)
|
|
170
|
+
if cached_root.is_dir():
|
|
171
|
+
plugin_root = _apply_repo_path(cached_root, repo_path)
|
|
172
|
+
return _checked_plugin(
|
|
173
|
+
plugin_root=plugin_root,
|
|
174
|
+
source=source,
|
|
175
|
+
repo_path=repo_path,
|
|
176
|
+
resolved_ref=resolved_ref.lower(),
|
|
177
|
+
secret_values=secret_values,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
temp_ctx: tempfile.TemporaryDirectory[str] | None = None
|
|
181
|
+
if work_dir is None:
|
|
182
|
+
temp_ctx = tempfile.TemporaryDirectory(prefix="openhands-plugin-fetch-")
|
|
183
|
+
work_root = Path(temp_ctx.name)
|
|
184
|
+
else:
|
|
185
|
+
work_root = work_dir
|
|
186
|
+
work_root.mkdir(parents=True, exist_ok=True)
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
fetched_root, fetched_ref = _fetch_git_checkout(
|
|
190
|
+
url=url,
|
|
191
|
+
ref=expanded_ref,
|
|
192
|
+
work_root=work_root,
|
|
193
|
+
timeout=timeout,
|
|
194
|
+
allow_file=allow_local,
|
|
195
|
+
secret_values=secret_values,
|
|
196
|
+
)
|
|
197
|
+
resolved_ref = fetched_ref.lower()
|
|
198
|
+
|
|
199
|
+
if use_cache and resolved_cache_dir is not None:
|
|
200
|
+
cached_root = _store_pinned_checkout(
|
|
201
|
+
url, resolved_cache_dir, resolved_ref, fetched_root
|
|
202
|
+
)
|
|
203
|
+
plugin_root = _apply_repo_path(cached_root, repo_path)
|
|
204
|
+
else:
|
|
205
|
+
plugin_root = _apply_repo_path(fetched_root, repo_path)
|
|
206
|
+
|
|
207
|
+
return _checked_plugin(
|
|
208
|
+
plugin_root=plugin_root,
|
|
209
|
+
source=source,
|
|
210
|
+
repo_path=repo_path,
|
|
211
|
+
resolved_ref=resolved_ref,
|
|
212
|
+
secret_values=secret_values,
|
|
213
|
+
)
|
|
214
|
+
finally:
|
|
215
|
+
if temp_ctx is not None:
|
|
216
|
+
temp_ctx.cleanup()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _secret_value(value: SecretValue) -> str | None:
|
|
220
|
+
if isinstance(value, str):
|
|
221
|
+
return value
|
|
222
|
+
if isinstance(value, SecretSource):
|
|
223
|
+
return value.get_value()
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _expand_source_and_ref(
|
|
228
|
+
source: str,
|
|
229
|
+
ref: str | None,
|
|
230
|
+
secrets: Mapping[str, SecretValue],
|
|
231
|
+
) -> tuple[str, str | None, list[str]]:
|
|
232
|
+
referenced = sorted(
|
|
233
|
+
{
|
|
234
|
+
match.group(1)
|
|
235
|
+
for value in (source, ref or "")
|
|
236
|
+
for match in _BRACED_VAR_RE.finditer(value)
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
resolved: dict[str, str] = {}
|
|
240
|
+
for name in referenced:
|
|
241
|
+
value = secrets.get(name)
|
|
242
|
+
plain_value = _secret_value(value) if value is not None else None
|
|
243
|
+
if plain_value is None:
|
|
244
|
+
raise PluginSourceError("missing_secret", f"Missing secret: {name}")
|
|
245
|
+
resolved[name] = plain_value
|
|
246
|
+
|
|
247
|
+
expanded_source = expand_variable_references(
|
|
248
|
+
source,
|
|
249
|
+
variables=resolved,
|
|
250
|
+
check_env=False,
|
|
251
|
+
support_unbraced=False,
|
|
252
|
+
expand_defaults=False,
|
|
253
|
+
)
|
|
254
|
+
expanded_ref = (
|
|
255
|
+
expand_variable_references(
|
|
256
|
+
ref,
|
|
257
|
+
variables=resolved,
|
|
258
|
+
check_env=False,
|
|
259
|
+
support_unbraced=False,
|
|
260
|
+
expand_defaults=False,
|
|
261
|
+
)
|
|
262
|
+
if ref is not None
|
|
263
|
+
else None
|
|
264
|
+
)
|
|
265
|
+
return expanded_source, expanded_ref, list(resolved.values())
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _copy_local_source(url: str, repo_path: str | None, work_dir: Path | None) -> Path:
|
|
269
|
+
source_root = Path(url).expanduser().resolve()
|
|
270
|
+
plugin_root = _apply_repo_path(source_root, repo_path)
|
|
271
|
+
if work_dir is None:
|
|
272
|
+
return plugin_root
|
|
273
|
+
destination = work_dir / "local-plugin"
|
|
274
|
+
shutil.copytree(plugin_root, destination, symlinks=True)
|
|
275
|
+
return destination
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _fetch_git_checkout(
|
|
279
|
+
*,
|
|
280
|
+
url: str,
|
|
281
|
+
ref: str | None,
|
|
282
|
+
work_root: Path,
|
|
283
|
+
timeout: int,
|
|
284
|
+
allow_file: bool,
|
|
285
|
+
secret_values: list[str],
|
|
286
|
+
) -> tuple[Path, str]:
|
|
287
|
+
repo_dir = work_root / "repo"
|
|
288
|
+
if repo_dir.exists():
|
|
289
|
+
shutil.rmtree(repo_dir)
|
|
290
|
+
repo_dir.mkdir(parents=True)
|
|
291
|
+
|
|
292
|
+
_run_git(["init"], repo_dir, timeout, allow_file, secret_values)
|
|
293
|
+
_run_git(
|
|
294
|
+
["remote", "add", "origin", url], repo_dir, timeout, allow_file, secret_values
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
fetch_ref = ref or "HEAD"
|
|
298
|
+
try:
|
|
299
|
+
_run_git(
|
|
300
|
+
["fetch", "--depth", "1", "origin", fetch_ref],
|
|
301
|
+
repo_dir,
|
|
302
|
+
timeout,
|
|
303
|
+
allow_file,
|
|
304
|
+
secret_values,
|
|
305
|
+
default_code="ref_not_found" if ref else "source_unavailable",
|
|
306
|
+
)
|
|
307
|
+
except PluginSourceError as exc:
|
|
308
|
+
if exc.code == "source_unavailable":
|
|
309
|
+
raise
|
|
310
|
+
raise
|
|
311
|
+
|
|
312
|
+
resolved_ref = _run_git(
|
|
313
|
+
["rev-parse", "FETCH_HEAD^{commit}"],
|
|
314
|
+
repo_dir,
|
|
315
|
+
timeout,
|
|
316
|
+
allow_file,
|
|
317
|
+
secret_values,
|
|
318
|
+
default_code="ref_not_found",
|
|
319
|
+
).strip()
|
|
320
|
+
if not _FULL_SHA_RE.fullmatch(resolved_ref):
|
|
321
|
+
raise PluginSourceError("ref_not_found", f"Ref not found: {ref or 'HEAD'}")
|
|
322
|
+
|
|
323
|
+
_run_git(
|
|
324
|
+
["checkout", "--detach", resolved_ref],
|
|
325
|
+
repo_dir,
|
|
326
|
+
timeout,
|
|
327
|
+
allow_file,
|
|
328
|
+
secret_values,
|
|
329
|
+
default_code="ref_not_found",
|
|
330
|
+
)
|
|
331
|
+
return repo_dir, resolved_ref
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _run_git(
|
|
335
|
+
args: list[str],
|
|
336
|
+
cwd: Path,
|
|
337
|
+
timeout: int,
|
|
338
|
+
allow_file: bool,
|
|
339
|
+
secret_values: list[str],
|
|
340
|
+
*,
|
|
341
|
+
default_code: str = "source_unavailable",
|
|
342
|
+
) -> str:
|
|
343
|
+
protocol_file = "always" if allow_file else "never"
|
|
344
|
+
cmd = [
|
|
345
|
+
"git",
|
|
346
|
+
"-c",
|
|
347
|
+
"credential.helper=",
|
|
348
|
+
"-c",
|
|
349
|
+
"core.askPass=",
|
|
350
|
+
"-c",
|
|
351
|
+
f"protocol.file.allow={protocol_file}",
|
|
352
|
+
*args,
|
|
353
|
+
]
|
|
354
|
+
env = os.environ.copy()
|
|
355
|
+
env.update(
|
|
356
|
+
{
|
|
357
|
+
"GIT_TERMINAL_PROMPT": "0",
|
|
358
|
+
"GIT_CONFIG_NOSYSTEM": "1",
|
|
359
|
+
"GIT_CONFIG_GLOBAL": os.devnull,
|
|
360
|
+
"GIT_ASKPASS": "true",
|
|
361
|
+
"SSH_ASKPASS": "true",
|
|
362
|
+
}
|
|
363
|
+
)
|
|
364
|
+
try:
|
|
365
|
+
result = subprocess.run(
|
|
366
|
+
cmd,
|
|
367
|
+
cwd=cwd,
|
|
368
|
+
env=env,
|
|
369
|
+
capture_output=True,
|
|
370
|
+
text=True,
|
|
371
|
+
errors="replace",
|
|
372
|
+
check=False,
|
|
373
|
+
timeout=timeout,
|
|
374
|
+
)
|
|
375
|
+
except subprocess.TimeoutExpired as exc:
|
|
376
|
+
raise PluginSourceError("timeout", "Git operation timed out.") from exc
|
|
377
|
+
except OSError as exc:
|
|
378
|
+
raise PluginSourceError("source_unavailable", f"Git failed: {exc}") from exc
|
|
379
|
+
|
|
380
|
+
if result.returncode == 0:
|
|
381
|
+
return result.stdout
|
|
382
|
+
|
|
383
|
+
stderr = _sanitize_message(result.stderr.strip(), secret_values)
|
|
384
|
+
raise PluginSourceError(_classify_git_error(stderr, default_code), stderr)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _classify_git_error(stderr: str, default_code: str) -> str:
|
|
388
|
+
lower = stderr.lower()
|
|
389
|
+
if "authentication failed" in lower or "authentication required" in lower:
|
|
390
|
+
return "auth_failed"
|
|
391
|
+
if (
|
|
392
|
+
"couldn't find remote ref" in lower
|
|
393
|
+
or "could not find remote ref" in lower
|
|
394
|
+
or "not our ref" in lower
|
|
395
|
+
or "unadvertised object" in lower
|
|
396
|
+
or "bad object" in lower
|
|
397
|
+
or "needed a single revision" in lower
|
|
398
|
+
):
|
|
399
|
+
return "ref_not_found"
|
|
400
|
+
if (
|
|
401
|
+
"could not read username" in lower
|
|
402
|
+
or "terminal prompts disabled" in lower
|
|
403
|
+
or "repository not found" in lower
|
|
404
|
+
or "could not resolve host" in lower
|
|
405
|
+
or "failed to connect" in lower
|
|
406
|
+
or "connection refused" in lower
|
|
407
|
+
or "does not appear to be a git repository" in lower
|
|
408
|
+
or "access rights" in lower
|
|
409
|
+
):
|
|
410
|
+
return "source_unavailable"
|
|
411
|
+
return default_code
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _sanitize_message(message: str, secret_values: list[str]) -> str:
|
|
415
|
+
sanitized = redact_url_credentials_in_text(message)
|
|
416
|
+
sanitized = redact_url_credentials(sanitized)
|
|
417
|
+
for value in secret_values:
|
|
418
|
+
if value:
|
|
419
|
+
sanitized = sanitized.replace(value, "****")
|
|
420
|
+
return sanitized
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _store_pinned_checkout(
|
|
424
|
+
source_url: str, cache_dir: Path, resolved_ref: str, fetched_root: Path
|
|
425
|
+
) -> Path:
|
|
426
|
+
target = get_pinned_plugin_cache_path(source_url, cache_dir, resolved_ref)
|
|
427
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
428
|
+
lock_path = target.parent / f"{target.name}.lock"
|
|
429
|
+
with FileLock(lock_path):
|
|
430
|
+
if target.is_dir():
|
|
431
|
+
return target
|
|
432
|
+
tmp_target = target.parent / f".{target.name}.{os.getpid()}.tmp"
|
|
433
|
+
if tmp_target.exists():
|
|
434
|
+
shutil.rmtree(tmp_target)
|
|
435
|
+
shutil.copytree(
|
|
436
|
+
fetched_root,
|
|
437
|
+
tmp_target,
|
|
438
|
+
symlinks=True,
|
|
439
|
+
ignore=shutil.ignore_patterns(".git"),
|
|
440
|
+
)
|
|
441
|
+
tmp_target.rename(target)
|
|
442
|
+
return target
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _apply_repo_path(root: Path, repo_path: str | None) -> Path:
|
|
446
|
+
if not repo_path:
|
|
447
|
+
return root
|
|
448
|
+
plugin_root = root / repo_path.strip("/")
|
|
449
|
+
if not resolves_within(plugin_root, root) or not plugin_root.is_dir():
|
|
450
|
+
raise PluginSourceError(
|
|
451
|
+
"repo_path_not_found", f"repo_path not found: {repo_path}"
|
|
452
|
+
)
|
|
453
|
+
return plugin_root
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _checked_plugin(
|
|
457
|
+
*,
|
|
458
|
+
plugin_root: Path,
|
|
459
|
+
source: str,
|
|
460
|
+
repo_path: str | None,
|
|
461
|
+
resolved_ref: str | None,
|
|
462
|
+
secret_values: list[str],
|
|
463
|
+
) -> CheckedPlugin:
|
|
464
|
+
manifest = _read_raw_manifest(plugin_root, secret_values)
|
|
465
|
+
try:
|
|
466
|
+
plugin = Plugin.load(plugin_root)
|
|
467
|
+
except Exception as exc:
|
|
468
|
+
message = _sanitize_message(str(exc), secret_values)
|
|
469
|
+
raise PluginSourceError("invalid_plugin", f"Invalid plugin: {message}") from exc
|
|
470
|
+
|
|
471
|
+
return CheckedPlugin(
|
|
472
|
+
path=plugin_root,
|
|
473
|
+
resolved_ref=resolved_ref,
|
|
474
|
+
source=source,
|
|
475
|
+
repo_path=repo_path,
|
|
476
|
+
manifest=manifest,
|
|
477
|
+
skills=[skill.name for skill in plugin.skills],
|
|
478
|
+
commands=[command.name for command in plugin.commands],
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _read_raw_manifest(plugin_root: Path, secret_values: list[str]) -> dict[str, Any]:
|
|
483
|
+
candidates = [
|
|
484
|
+
plugin_root / "plugin.json",
|
|
485
|
+
plugin_root / ".plugin" / "plugin.json",
|
|
486
|
+
plugin_root / ".claude-plugin" / "plugin.json",
|
|
487
|
+
]
|
|
488
|
+
for candidate in candidates:
|
|
489
|
+
if not candidate.is_file() or not resolves_within(candidate, plugin_root):
|
|
490
|
+
continue
|
|
491
|
+
try:
|
|
492
|
+
data = json.loads(candidate.read_text(encoding="utf-8-sig"))
|
|
493
|
+
except (json.JSONDecodeError, OSError, UnicodeDecodeError) as exc:
|
|
494
|
+
message = _sanitize_message(str(exc), secret_values)
|
|
495
|
+
raise PluginSourceError(
|
|
496
|
+
"invalid_plugin", f"Invalid plugin manifest: {message}"
|
|
497
|
+
) from exc
|
|
498
|
+
if not isinstance(data, dict):
|
|
499
|
+
raise PluginSourceError(
|
|
500
|
+
"invalid_plugin",
|
|
501
|
+
"Invalid plugin manifest: plugin.json must be an object.",
|
|
502
|
+
)
|
|
503
|
+
return data
|
|
504
|
+
raise PluginSourceError("invalid_plugin", "Invalid plugin: plugin.json not found.")
|
|
@@ -5,6 +5,8 @@ from openhands.sdk.profiles.agent_profile import (
|
|
|
5
5
|
ACPAgentProfile,
|
|
6
6
|
AgentProfile,
|
|
7
7
|
AgentProfileBase,
|
|
8
|
+
AgentProfileInfo,
|
|
9
|
+
AgentProfileListResponse,
|
|
8
10
|
LaunchedAgentProfile,
|
|
9
11
|
OpenHandsAgentProfile,
|
|
10
12
|
ProfileVerificationSettings,
|
|
@@ -43,6 +45,8 @@ __all__ = [
|
|
|
43
45
|
"ACPAgentProfile",
|
|
44
46
|
"AgentProfile",
|
|
45
47
|
"AgentProfileBase",
|
|
48
|
+
"AgentProfileInfo",
|
|
49
|
+
"AgentProfileListResponse",
|
|
46
50
|
"AgentProfileDiagnostics",
|
|
47
51
|
"AgentProfileStore",
|
|
48
52
|
"AgentProfileStoreProtocol",
|