openhands-sdk 1.29.0__tar.gz → 1.29.2__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.29.0 → openhands_sdk-1.29.2}/PKG-INFO +3 -1
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/base.py +37 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/agent_context.py +15 -3
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/impl/local_conversation.py +242 -29
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/impl/api/critic.py +1 -1
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/llm.py +33 -1
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/model_features.py +6 -0
- openhands_sdk-1.29.2/openhands/sdk/llm/utils/vertex_preflight.py +36 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/marketplace/__init__.py +27 -14
- openhands_sdk-1.29.2/openhands/sdk/marketplace/registration.py +47 -0
- openhands_sdk-1.29.2/openhands/sdk/marketplace/registry.py +231 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands_sdk.egg-info/PKG-INFO +3 -1
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands_sdk.egg-info/SOURCES.txt +6 -4
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands_sdk.egg-info/requires.txt +3 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/pyproject.toml +2 -1
- openhands_sdk-1.29.0/openhands/sdk/conversation/serialization_diff.py +0 -0
- openhands_sdk-1.29.0/openhands/sdk/utils/pydantic_diff.py +0 -85
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/acp_agent.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/agent.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/in_context_learning_example.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/security_policy.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/security_risk_assessment.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/self_documentation.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/system_prompt.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/system_prompt_interactive.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/system_prompt_planning.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/response_dispatch.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/agent/utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/prompts/presets.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/prompts/sections/static.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/prompts/templates/system_message_suffix.j2 +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/base.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/conversation.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/impl/remote_conversation.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/request.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/secret_registry.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/state.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/acp_tool_call.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/hook_execution.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/extensions/fetch.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/extensions/installation/manager.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/git/git_changes.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/auth/openai.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/exceptions/classifier.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/llm_profile_store.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/llm/utils/verified_models.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/plugin/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/plugin/fetch.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/profiles/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/profiles/agent_profile.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/profiles/agent_profile_store.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/profiles/profile_refs.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/profiles/resolver.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/grayswan/analyzer.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/settings/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/settings/acp_providers.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/settings/api_models.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/settings/model.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/skills/installed.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/skills/skill.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/skills/utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/subagent/load.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/subagent/registry.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/subagent/schema.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/tool/builtins/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/redact.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/workspace/remote/base.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/workspace/repo.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.29.
|
|
3
|
+
Version: 1.29.2
|
|
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
|
|
@@ -26,3 +26,5 @@ Requires-Dist: tree-sitter>=0.25
|
|
|
26
26
|
Requires-Dist: tree-sitter-bash>=0.25
|
|
27
27
|
Provides-Extra: boto3
|
|
28
28
|
Requires-Dist: boto3>=1.35.0; extra == "boto3"
|
|
29
|
+
Provides-Extra: vertex
|
|
30
|
+
Requires-Dist: google-cloud-aiplatform>=1.38; extra == "vertex"
|
|
@@ -6,6 +6,7 @@ import os
|
|
|
6
6
|
import re
|
|
7
7
|
import sys
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
|
+
from collections import Counter
|
|
9
10
|
from collections.abc import Generator, Iterable, Sequence
|
|
10
11
|
from concurrent.futures import ThreadPoolExecutor
|
|
11
12
|
from typing import TYPE_CHECKING, Any, Literal
|
|
@@ -901,6 +902,42 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
901
902
|
# Drive the traversal from self
|
|
902
903
|
yield from _walk(self)
|
|
903
904
|
|
|
905
|
+
def _close_tool_executor(self, tool: ToolDefinition) -> None:
|
|
906
|
+
try:
|
|
907
|
+
executable_tool = tool.as_executable()
|
|
908
|
+
executable_tool.executor.close()
|
|
909
|
+
except NotImplementedError:
|
|
910
|
+
return
|
|
911
|
+
except Exception as exc:
|
|
912
|
+
logger.warning("Error closing executor for tool '%s': %s", tool.name, exc)
|
|
913
|
+
|
|
914
|
+
def add_runtime_tools(self, tools: Sequence[ToolDefinition]) -> None:
|
|
915
|
+
if not self._initialized:
|
|
916
|
+
logger.warning(
|
|
917
|
+
"add_runtime_tools called before agent initialization; "
|
|
918
|
+
"tools will not be registered"
|
|
919
|
+
)
|
|
920
|
+
return
|
|
921
|
+
for tool in tools:
|
|
922
|
+
if not isinstance(tool, ToolDefinition):
|
|
923
|
+
raise ValueError(
|
|
924
|
+
f"Tool {tool} is not an instance of 'ToolDefinition'. "
|
|
925
|
+
f"Got type: {type(tool)}"
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
tool_names = [tool.name for tool in tools]
|
|
929
|
+
if len(tool_names) != len(set(tool_names)):
|
|
930
|
+
duplicates = {
|
|
931
|
+
name for name, count in Counter(tool_names).items() if count > 1
|
|
932
|
+
}
|
|
933
|
+
raise ValueError(f"Duplicate runtime tool names found: {duplicates}")
|
|
934
|
+
|
|
935
|
+
for tool in tools:
|
|
936
|
+
previous_tool = self._tools.get(tool.name)
|
|
937
|
+
if previous_tool is not None:
|
|
938
|
+
self._close_tool_executor(previous_tool)
|
|
939
|
+
self._tools[tool.name] = tool
|
|
940
|
+
|
|
904
941
|
@property
|
|
905
942
|
def tools_map(self) -> dict[str, ToolDefinition]:
|
|
906
943
|
"""Get the initialized tools map.
|
|
@@ -19,6 +19,7 @@ from openhands.sdk.context.prompts import render_template
|
|
|
19
19
|
from openhands.sdk.llm import Message, TextContent
|
|
20
20
|
from openhands.sdk.llm.utils.model_prompt_spec import get_model_prompt_spec
|
|
21
21
|
from openhands.sdk.logger import get_logger
|
|
22
|
+
from openhands.sdk.marketplace.registration import MarketplaceRegistration
|
|
22
23
|
from openhands.sdk.secret import SecretSource, SecretValue
|
|
23
24
|
from openhands.sdk.skills import (
|
|
24
25
|
Skill,
|
|
@@ -111,6 +112,14 @@ class AgentContext(BaseModel):
|
|
|
111
112
|
),
|
|
112
113
|
json_schema_extra={"acp_compatible": True},
|
|
113
114
|
)
|
|
115
|
+
registered_marketplaces: list[MarketplaceRegistration] = Field(
|
|
116
|
+
default_factory=list,
|
|
117
|
+
description=(
|
|
118
|
+
"Marketplace registrations for plugin resolution. Registrations with "
|
|
119
|
+
"auto_load=True are resolved by LocalConversation at startup."
|
|
120
|
+
),
|
|
121
|
+
json_schema_extra={"acp_compatible": True},
|
|
122
|
+
)
|
|
114
123
|
load_project_skills: bool = Field(
|
|
115
124
|
default=False,
|
|
116
125
|
description=(
|
|
@@ -206,15 +215,18 @@ class AgentContext(BaseModel):
|
|
|
206
215
|
|
|
207
216
|
@model_validator(mode="after")
|
|
208
217
|
def _load_auto_skills(self):
|
|
209
|
-
"""Load user and/or public skills if enabled."""
|
|
210
|
-
|
|
218
|
+
"""Load user and/or legacy public skills if enabled."""
|
|
219
|
+
# Any marketplace registration opts the context out of the legacy
|
|
220
|
+
# public-skills path, even when the registration is resolution-only.
|
|
221
|
+
include_public = self.load_public_skills and not self.registered_marketplaces
|
|
222
|
+
if not self.load_user_skills and not include_public:
|
|
211
223
|
return self
|
|
212
224
|
|
|
213
225
|
auto_skills = load_available_skills(
|
|
214
226
|
work_dir=None,
|
|
215
227
|
include_user=self.load_user_skills,
|
|
216
228
|
include_project=False,
|
|
217
|
-
include_public=
|
|
229
|
+
include_public=include_public,
|
|
218
230
|
marketplace_path=self.marketplace_path,
|
|
219
231
|
)
|
|
220
232
|
|
{openhands_sdk-1.29.0 → openhands_sdk-1.29.2}/openhands/sdk/conversation/impl/local_conversation.py
RENAMED
|
@@ -53,6 +53,8 @@ from openhands.sdk.llm.auth.openai import create_subscription_llm_from_config
|
|
|
53
53
|
from openhands.sdk.llm.llm_profile_store import LLMProfileStore
|
|
54
54
|
from openhands.sdk.llm.llm_registry import LLMRegistry
|
|
55
55
|
from openhands.sdk.logger import get_logger
|
|
56
|
+
from openhands.sdk.marketplace.registry import MarketplaceRegistry
|
|
57
|
+
from openhands.sdk.mcp import create_mcp_tools
|
|
56
58
|
from openhands.sdk.observability.laminar import observe
|
|
57
59
|
from openhands.sdk.plugin import (
|
|
58
60
|
Plugin,
|
|
@@ -75,6 +77,8 @@ from openhands.sdk.subagent import (
|
|
|
75
77
|
register_file_agents,
|
|
76
78
|
register_plugin_agents,
|
|
77
79
|
)
|
|
80
|
+
from openhands.sdk.tool import ToolDefinition
|
|
81
|
+
from openhands.sdk.tool.builtins import InvokeSkillTool
|
|
78
82
|
from openhands.sdk.tool.client_tool import ClientToolSpec
|
|
79
83
|
from openhands.sdk.tool.schema import Action, Observation
|
|
80
84
|
from openhands.sdk.utils.cipher import Cipher
|
|
@@ -86,6 +90,8 @@ logger = get_logger(__name__)
|
|
|
86
90
|
ACP_LAST_PROMPT_USER_MESSAGE_ID = "acp_last_prompt_user_message_id"
|
|
87
91
|
ACP_INFLIGHT_PROMPT_USER_MESSAGE_ID = "acp_inflight_prompt_user_message_id"
|
|
88
92
|
ACP_SUPERSEDE_INFLIGHT_PROMPT = "acp_supersede_inflight_prompt"
|
|
93
|
+
_RUNTIME_MCP_TIMEOUT_SECS = 30
|
|
94
|
+
|
|
89
95
|
ACP_STOP_HOOK_FEEDBACK_PREFIX = "[Stop hook feedback]"
|
|
90
96
|
|
|
91
97
|
|
|
@@ -663,35 +669,58 @@ class LocalConversation(BaseConversation):
|
|
|
663
669
|
# Track whether we have plugins or MCP config to process
|
|
664
670
|
has_mcp_config = bool(merged_mcp)
|
|
665
671
|
|
|
666
|
-
|
|
672
|
+
plugins_to_load: list[tuple[PluginSource, bool]] = []
|
|
673
|
+
if merged_context is not None and merged_context.registered_marketplaces:
|
|
674
|
+
registrations = [
|
|
675
|
+
registration.model_copy(
|
|
676
|
+
update={
|
|
677
|
+
"source": self._expand_plugin_source_ref(registration.source),
|
|
678
|
+
"ref": self._expand_plugin_source_ref(registration.ref)
|
|
679
|
+
if registration.ref
|
|
680
|
+
else None,
|
|
681
|
+
}
|
|
682
|
+
)
|
|
683
|
+
for registration in merged_context.registered_marketplaces
|
|
684
|
+
]
|
|
685
|
+
registry = MarketplaceRegistry(registrations)
|
|
686
|
+
for registration in registry.get_auto_load_registrations():
|
|
687
|
+
try:
|
|
688
|
+
marketplace, _ = registry.get_marketplace(registration.name)
|
|
689
|
+
except Exception:
|
|
690
|
+
logger.warning(
|
|
691
|
+
"Failed to load marketplace '%s'; continuing without it",
|
|
692
|
+
registration.name,
|
|
693
|
+
exc_info=True,
|
|
694
|
+
)
|
|
695
|
+
continue
|
|
696
|
+
for entry in marketplace.plugins:
|
|
697
|
+
source, ref, repo_path = marketplace.resolve_plugin_source(entry)
|
|
698
|
+
plugins_to_load.append(
|
|
699
|
+
(
|
|
700
|
+
PluginSource(source=source, ref=ref, repo_path=repo_path),
|
|
701
|
+
True,
|
|
702
|
+
)
|
|
703
|
+
)
|
|
704
|
+
|
|
667
705
|
if self._plugin_specs:
|
|
668
|
-
|
|
669
|
-
self._resolved_plugins = []
|
|
706
|
+
plugins_to_load.extend((spec, False) for spec in self._plugin_specs)
|
|
670
707
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
# SECURITY: secrets only (check_env=False) -- never fold host
|
|
676
|
-
# environment variables into a URL sent to a remote git host.
|
|
677
|
-
# Braced-only (support_unbraced=False) avoids mangling a literal "$"
|
|
678
|
-
# that may legitimately appear in a token/password. expand_defaults
|
|
679
|
-
# is False so an unknown ${VAR} is left verbatim rather than silently
|
|
680
|
-
# defaulted inside a URL.
|
|
681
|
-
get_secret = self._state.secret_registry.get_secret_value
|
|
682
|
-
|
|
683
|
-
def _expand_secret_refs(value: str) -> str:
|
|
684
|
-
return expand_variable_references(
|
|
685
|
-
value,
|
|
686
|
-
get_secret=get_secret,
|
|
687
|
-
check_env=False,
|
|
688
|
-
support_unbraced=False,
|
|
689
|
-
expand_defaults=False,
|
|
690
|
-
)
|
|
708
|
+
# Load plugins if specified or registered for auto-load
|
|
709
|
+
if plugins_to_load:
|
|
710
|
+
logger.info(f"Loading {len(plugins_to_load)} plugin(s)...")
|
|
711
|
+
self._resolved_plugins = []
|
|
691
712
|
|
|
692
|
-
for spec in
|
|
693
|
-
|
|
694
|
-
|
|
713
|
+
for spec, source_refs_expanded in plugins_to_load:
|
|
714
|
+
if source_refs_expanded:
|
|
715
|
+
fetch_source = spec.source
|
|
716
|
+
fetch_ref = spec.ref
|
|
717
|
+
else:
|
|
718
|
+
fetch_source = self._expand_plugin_source_ref(spec.source)
|
|
719
|
+
fetch_ref = (
|
|
720
|
+
self._expand_plugin_source_ref(spec.ref)
|
|
721
|
+
if spec.ref
|
|
722
|
+
else spec.ref
|
|
723
|
+
)
|
|
695
724
|
|
|
696
725
|
# Fetch plugin and get resolved commit SHA
|
|
697
726
|
path, resolved_ref = fetch_plugin_with_resolution(
|
|
@@ -711,7 +740,7 @@ class LocalConversation(BaseConversation):
|
|
|
711
740
|
# Load the plugin
|
|
712
741
|
plugin = Plugin.load(path)
|
|
713
742
|
logger.debug(
|
|
714
|
-
f"Loaded plugin '{plugin.manifest.name}'
|
|
743
|
+
f"Loaded plugin '{plugin.manifest.name}'"
|
|
715
744
|
+ (f" @ {resolved_ref[:8]}" if resolved_ref else "")
|
|
716
745
|
)
|
|
717
746
|
|
|
@@ -728,7 +757,7 @@ class LocalConversation(BaseConversation):
|
|
|
728
757
|
if plugin.agents:
|
|
729
758
|
all_plugin_agents.extend(plugin.agents)
|
|
730
759
|
|
|
731
|
-
logger.info(f"Loaded {len(
|
|
760
|
+
logger.info(f"Loaded {len(plugins_to_load)} plugin(s) via Conversation")
|
|
732
761
|
|
|
733
762
|
# Resolve project skills from the workspace. AgentContext can't do this
|
|
734
763
|
# itself (the workspace path is unknown at validation time), so it is done
|
|
@@ -781,7 +810,7 @@ class LocalConversation(BaseConversation):
|
|
|
781
810
|
|
|
782
811
|
# Update agent with merged content only if something changed.
|
|
783
812
|
# Skip update otherwise to avoid unnecessary agent state mutations.
|
|
784
|
-
if
|
|
813
|
+
if plugins_to_load or has_mcp_config or project_skills_loaded:
|
|
785
814
|
self.agent = self.agent.model_copy(
|
|
786
815
|
update={
|
|
787
816
|
"agent_context": merged_context,
|
|
@@ -840,6 +869,190 @@ class LocalConversation(BaseConversation):
|
|
|
840
869
|
|
|
841
870
|
self._plugins_loaded = True
|
|
842
871
|
|
|
872
|
+
def _expand_plugin_source_ref(self, value: str) -> str:
|
|
873
|
+
return expand_variable_references(
|
|
874
|
+
value,
|
|
875
|
+
get_secret=self._state.secret_registry.get_secret_value,
|
|
876
|
+
check_env=False,
|
|
877
|
+
support_unbraced=False,
|
|
878
|
+
expand_defaults=False,
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
def _marketplace_registry_from_context(self) -> MarketplaceRegistry:
|
|
882
|
+
agent_context = self.agent.agent_context
|
|
883
|
+
if agent_context is None:
|
|
884
|
+
raise ValueError(
|
|
885
|
+
"No agent context available. Configure agent_context with "
|
|
886
|
+
"registered_marketplaces to use load_plugin()."
|
|
887
|
+
)
|
|
888
|
+
registrations = agent_context.registered_marketplaces
|
|
889
|
+
if not registrations:
|
|
890
|
+
raise ValueError(
|
|
891
|
+
"No marketplaces registered. Configure registered_marketplaces "
|
|
892
|
+
"in AgentContext to use load_plugin()."
|
|
893
|
+
)
|
|
894
|
+
return MarketplaceRegistry(
|
|
895
|
+
[
|
|
896
|
+
registration.model_copy(
|
|
897
|
+
update={
|
|
898
|
+
"source": self._expand_plugin_source_ref(registration.source),
|
|
899
|
+
"ref": self._expand_plugin_source_ref(registration.ref)
|
|
900
|
+
if registration.ref
|
|
901
|
+
else None,
|
|
902
|
+
}
|
|
903
|
+
)
|
|
904
|
+
for registration in registrations
|
|
905
|
+
]
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
def _merge_runtime_plugin_hooks(self, plugin_hooks: HookConfig) -> None:
|
|
909
|
+
existing_config = self._state.hook_config
|
|
910
|
+
merged_config = (
|
|
911
|
+
HookConfig.merge([existing_config, plugin_hooks])
|
|
912
|
+
if existing_config is not None
|
|
913
|
+
else plugin_hooks
|
|
914
|
+
)
|
|
915
|
+
if merged_config is None:
|
|
916
|
+
return
|
|
917
|
+
|
|
918
|
+
hook_persistence_dir = (
|
|
919
|
+
str(Path(self._state.persistence_dir).parent)
|
|
920
|
+
if self._state.persistence_dir is not None
|
|
921
|
+
else None
|
|
922
|
+
)
|
|
923
|
+
previous_processor = self._hook_processor
|
|
924
|
+
if previous_processor is not None:
|
|
925
|
+
previous_processor.run_session_end()
|
|
926
|
+
|
|
927
|
+
self._state.hook_config = merged_config
|
|
928
|
+
self._pending_hook_config = merged_config
|
|
929
|
+
self._hook_processor, self._on_event = create_hook_callback(
|
|
930
|
+
hook_config=merged_config,
|
|
931
|
+
working_dir=str(self.workspace.working_dir),
|
|
932
|
+
session_id=str(self._state.id),
|
|
933
|
+
original_callback=self._base_callback,
|
|
934
|
+
llm_getter=lambda: self.agent.llm,
|
|
935
|
+
persistence_dir=hook_persistence_dir,
|
|
936
|
+
visualizer=self._visualizer,
|
|
937
|
+
conversation_stats=self._state.stats,
|
|
938
|
+
)
|
|
939
|
+
self._hook_processor.set_conversation_state(self._state)
|
|
940
|
+
self._hook_processor.run_session_start()
|
|
941
|
+
|
|
942
|
+
def _runtime_mcp_tools_for_plugin(
|
|
943
|
+
self, plugin_mcp_config: dict[str, Any] | None
|
|
944
|
+
) -> list[ToolDefinition]:
|
|
945
|
+
if not plugin_mcp_config:
|
|
946
|
+
return []
|
|
947
|
+
return list(
|
|
948
|
+
create_mcp_tools(plugin_mcp_config, _RUNTIME_MCP_TIMEOUT_SECS).tools
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
def _runtime_skill_tools_for_agent(self) -> list[ToolDefinition]:
|
|
952
|
+
agent_context = self.agent.agent_context
|
|
953
|
+
has_invocable_skills = bool(
|
|
954
|
+
agent_context
|
|
955
|
+
and any(
|
|
956
|
+
skill.is_agentskills_format and not skill.disable_model_invocation
|
|
957
|
+
for skill in agent_context.skills
|
|
958
|
+
)
|
|
959
|
+
)
|
|
960
|
+
if has_invocable_skills and InvokeSkillTool.name not in self.agent.tools_map:
|
|
961
|
+
return list(InvokeSkillTool.create(self._state))
|
|
962
|
+
return []
|
|
963
|
+
|
|
964
|
+
def _close_runtime_tools(self, tools: Sequence[ToolDefinition]) -> None:
|
|
965
|
+
for tool in tools:
|
|
966
|
+
try:
|
|
967
|
+
tool.as_executable().executor.close()
|
|
968
|
+
except NotImplementedError:
|
|
969
|
+
continue
|
|
970
|
+
except Exception as exc:
|
|
971
|
+
logger.warning(
|
|
972
|
+
"Error closing runtime tool executor for tool '%s': %s",
|
|
973
|
+
tool.name,
|
|
974
|
+
exc,
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
def load_plugin(self, plugin_ref: str) -> None:
|
|
978
|
+
"""Load a plugin from the conversation's registered marketplaces."""
|
|
979
|
+
self._ensure_plugins_loaded()
|
|
980
|
+
spec = self._marketplace_registry_from_context().resolve_plugin(plugin_ref)
|
|
981
|
+
|
|
982
|
+
fetch_source = self._expand_plugin_source_ref(spec.source)
|
|
983
|
+
fetch_ref = self._expand_plugin_source_ref(spec.ref) if spec.ref else spec.ref
|
|
984
|
+
path, resolved_ref = fetch_plugin_with_resolution(
|
|
985
|
+
source=fetch_source,
|
|
986
|
+
ref=fetch_ref,
|
|
987
|
+
repo_path=spec.repo_path,
|
|
988
|
+
)
|
|
989
|
+
plugin = Plugin.load(path)
|
|
990
|
+
logger.info(
|
|
991
|
+
f"Loaded plugin '{plugin.manifest.name}'"
|
|
992
|
+
+ (f" @ {resolved_ref[:8]}" if resolved_ref else "")
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
get_secret = self._state.secret_registry.get_secret_value
|
|
996
|
+
runtime_plugin_mcp = (
|
|
997
|
+
expand_mcp_variables(
|
|
998
|
+
plugin.mcp_config,
|
|
999
|
+
{},
|
|
1000
|
+
get_secret=get_secret,
|
|
1001
|
+
expand_defaults=True,
|
|
1002
|
+
)
|
|
1003
|
+
if plugin.mcp_config
|
|
1004
|
+
else None
|
|
1005
|
+
)
|
|
1006
|
+
merged_context = plugin.add_skills_to(self.agent.agent_context)
|
|
1007
|
+
merged_mcp = plugin.add_mcp_config_to(
|
|
1008
|
+
dict(self.agent.mcp_config) if self.agent.mcp_config else {}
|
|
1009
|
+
)
|
|
1010
|
+
if merged_mcp:
|
|
1011
|
+
merged_mcp = expand_mcp_variables(
|
|
1012
|
+
merged_mcp,
|
|
1013
|
+
{},
|
|
1014
|
+
get_secret=get_secret,
|
|
1015
|
+
expand_defaults=True,
|
|
1016
|
+
)
|
|
1017
|
+
runtime_mcp_tools = (
|
|
1018
|
+
self._runtime_mcp_tools_for_plugin(runtime_plugin_mcp)
|
|
1019
|
+
if self._agent_ready
|
|
1020
|
+
else []
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
with self._state:
|
|
1024
|
+
self.agent = self.agent.model_copy(
|
|
1025
|
+
update={
|
|
1026
|
+
"agent_context": merged_context,
|
|
1027
|
+
"mcp_config": merged_mcp,
|
|
1028
|
+
}
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
if plugin.agents:
|
|
1032
|
+
register_plugin_agents(
|
|
1033
|
+
agents=plugin.agents,
|
|
1034
|
+
work_dir=self.workspace.working_dir,
|
|
1035
|
+
)
|
|
1036
|
+
if plugin.hooks and not plugin.hooks.is_empty():
|
|
1037
|
+
self._merge_runtime_plugin_hooks(plugin.hooks)
|
|
1038
|
+
|
|
1039
|
+
resolved = ResolvedPluginSource.from_plugin_source(spec, resolved_ref)
|
|
1040
|
+
if self._resolved_plugins is None:
|
|
1041
|
+
self._resolved_plugins = []
|
|
1042
|
+
self._resolved_plugins.append(resolved)
|
|
1043
|
+
|
|
1044
|
+
self._state.agent = self.agent
|
|
1045
|
+
if self._agent_ready:
|
|
1046
|
+
runtime_tools = [
|
|
1047
|
+
*runtime_mcp_tools,
|
|
1048
|
+
*self._runtime_skill_tools_for_agent(),
|
|
1049
|
+
]
|
|
1050
|
+
try:
|
|
1051
|
+
self.agent.add_runtime_tools(runtime_tools)
|
|
1052
|
+
except Exception:
|
|
1053
|
+
self._close_runtime_tools(runtime_mcp_tools)
|
|
1054
|
+
raise
|
|
1055
|
+
|
|
843
1056
|
def _register_file_based_agents(self) -> None:
|
|
844
1057
|
"""Discover and register file-based agents into the agent registry.
|
|
845
1058
|
|
|
@@ -12,7 +12,7 @@ from openhands.sdk.critic.impl.api.taxonomy import categorize_features
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
-
from openhands.sdk.event import LLMConvertibleEvent
|
|
15
|
+
from openhands.sdk.event import LLMConvertibleEvent
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def _format_feature_list(features: list[dict[str, Any]]) -> str:
|
|
@@ -10,13 +10,13 @@ from collections.abc import Callable, Sequence
|
|
|
10
10
|
from contextlib import contextmanager
|
|
11
11
|
from typing import TYPE_CHECKING, Any, ClassVar, Literal, get_args, get_origin
|
|
12
12
|
|
|
13
|
-
import httpx # noqa: F401
|
|
14
13
|
from pydantic import (
|
|
15
14
|
BaseModel,
|
|
16
15
|
ConfigDict,
|
|
17
16
|
Field,
|
|
18
17
|
PrivateAttr,
|
|
19
18
|
SecretStr,
|
|
19
|
+
computed_field,
|
|
20
20
|
field_serializer,
|
|
21
21
|
field_validator,
|
|
22
22
|
model_validator,
|
|
@@ -120,6 +120,7 @@ from openhands.sdk.llm.utils.openhands_provider import (
|
|
|
120
120
|
)
|
|
121
121
|
from openhands.sdk.llm.utils.retry_mixin import RetryMixin
|
|
122
122
|
from openhands.sdk.llm.utils.telemetry import Telemetry
|
|
123
|
+
from openhands.sdk.llm.utils.vertex_preflight import assert_vertex_sdk_available
|
|
123
124
|
from openhands.sdk.logger import ENV_LOG_DIR, get_logger
|
|
124
125
|
|
|
125
126
|
|
|
@@ -279,6 +280,8 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
279
280
|
default=None,
|
|
280
281
|
)
|
|
281
282
|
|
|
283
|
+
# OpenRouter uses HTTP-Referer as the app identity for rankings.
|
|
284
|
+
# Keep this stable unless the OpenRouter app attribution is migrated.
|
|
282
285
|
openrouter_site_url: str = Field(
|
|
283
286
|
default="https://docs.all-hands.dev/",
|
|
284
287
|
)
|
|
@@ -725,6 +728,14 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
725
728
|
)
|
|
726
729
|
return self._telemetry
|
|
727
730
|
|
|
731
|
+
@computed_field(
|
|
732
|
+
return_type=bool,
|
|
733
|
+
description=(
|
|
734
|
+
"Whether this LLM uses subscription-based authentication. "
|
|
735
|
+
"Serialized so that subscription-specific request handling "
|
|
736
|
+
"survives transport to a remote agent-server."
|
|
737
|
+
),
|
|
738
|
+
)
|
|
728
739
|
@property
|
|
729
740
|
def is_subscription(self) -> bool:
|
|
730
741
|
"""Check if this LLM uses subscription-based authentication.
|
|
@@ -738,6 +749,24 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
738
749
|
"""
|
|
739
750
|
return self._is_subscription
|
|
740
751
|
|
|
752
|
+
@model_validator(mode="wrap")
|
|
753
|
+
@classmethod
|
|
754
|
+
def _restore_is_subscription(cls, data, handler):
|
|
755
|
+
"""Restore the subscription flag when validating serialized data.
|
|
756
|
+
|
|
757
|
+
``is_subscription`` is a computed field backed by the private
|
|
758
|
+
``_is_subscription`` attribute, so plain validation would drop it.
|
|
759
|
+
Without this, an LLM created via ``LLM.subscription_login()`` loses
|
|
760
|
+
its subscription-specific request handling (streaming exemption,
|
|
761
|
+
Codex system prompt transform, reasoning-item stripping) after a
|
|
762
|
+
dump/validate round trip - e.g. when shipped to a remote
|
|
763
|
+
agent-server.
|
|
764
|
+
"""
|
|
765
|
+
llm = handler(data)
|
|
766
|
+
if isinstance(data, dict) and data.get("is_subscription"):
|
|
767
|
+
llm._is_subscription = True
|
|
768
|
+
return llm
|
|
769
|
+
|
|
741
770
|
def restore_metrics(self, metrics: Metrics) -> None:
|
|
742
771
|
# Only used by ConversationStats to seed metrics
|
|
743
772
|
self._metrics = metrics
|
|
@@ -1890,6 +1919,9 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
1890
1919
|
**kwargs,
|
|
1891
1920
|
) -> dict[str, Any]:
|
|
1892
1921
|
"""Build the keyword arguments for a litellm (a)completion call."""
|
|
1922
|
+
provider = self._infer_litellm_provider()
|
|
1923
|
+
assert_vertex_sdk_available(provider)
|
|
1924
|
+
|
|
1893
1925
|
# When streaming, request usage in the final chunk so that detailed
|
|
1894
1926
|
# token breakdowns (prompt_tokens_details with cached_tokens, etc.) are
|
|
1895
1927
|
# not silently discarded by litellm's streaming handler.
|
|
@@ -102,6 +102,12 @@ def _normalized_supported_openai_params(model: str | None) -> frozenset[str]:
|
|
|
102
102
|
REASONING_EFFORT_MODELS: list[str] = [
|
|
103
103
|
# https://www.anthropic.com/news/claude-fable-5
|
|
104
104
|
"claude-fable-5",
|
|
105
|
+
# LiteLLM recognizes the first-party "anthropic/claude-opus-4-8" id, but not
|
|
106
|
+
# the Bedrock cross-region inference ids (e.g.
|
|
107
|
+
# "bedrock/us.anthropic.claude-opus-4-8-v1:0"), which fall through to the
|
|
108
|
+
# non-reasoning branch and leak temperature/top_p. List explicitly until
|
|
109
|
+
# LiteLLM ships Bedrock metadata for this model.
|
|
110
|
+
"claude-opus-4-8",
|
|
105
111
|
]
|
|
106
112
|
|
|
107
113
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Preflight check for Vertex AI partner-model dependencies.
|
|
2
|
+
|
|
3
|
+
`google-cloud-aiplatform` is an optional extra (`openhands-sdk[vertex]`). When a
|
|
4
|
+
caller targets a `vertex_ai/*` model without the extra installed, LiteLLM fails
|
|
5
|
+
with a low-level `ModuleNotFoundError` from inside its provider handler. We
|
|
6
|
+
catch that earlier and surface a friendly install hint instead.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import importlib.util
|
|
12
|
+
|
|
13
|
+
from openhands.sdk.llm.exceptions import LLMBadRequestError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_INSTALL_HINT = (
|
|
17
|
+
"Vertex AI partner models require the Vertex SDK. "
|
|
18
|
+
'Install with: pip install "openhands-sdk[vertex]"'
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _vertex_sdk_available() -> bool:
|
|
23
|
+
return importlib.util.find_spec("vertexai") is not None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def assert_vertex_sdk_available(provider: str | None) -> None:
|
|
27
|
+
"""Raise a friendly error if the caller is targeting Vertex without the SDK.
|
|
28
|
+
|
|
29
|
+
No-op for any non-`vertex_ai` provider, so it's safe to call unconditionally
|
|
30
|
+
from the transport path.
|
|
31
|
+
"""
|
|
32
|
+
if provider != "vertex_ai":
|
|
33
|
+
return
|
|
34
|
+
if _vertex_sdk_available():
|
|
35
|
+
return
|
|
36
|
+
raise LLMBadRequestError(_INSTALL_HINT)
|
|
@@ -23,27 +23,40 @@ Example marketplace.json:
|
|
|
23
23
|
```
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
from
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
MarketplaceMetadata,
|
|
32
|
-
MarketplaceOwner,
|
|
33
|
-
MarketplacePluginEntry,
|
|
34
|
-
MarketplacePluginSource,
|
|
26
|
+
from importlib import import_module
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from openhands.sdk.marketplace.registration import (
|
|
30
|
+
MarketplaceRegistration as MarketplaceRegistration,
|
|
35
31
|
)
|
|
36
32
|
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
# Constants
|
|
34
|
+
_TYPE_EXPORTS = {
|
|
40
35
|
"MARKETPLACE_MANIFEST_DIRS",
|
|
41
36
|
"MARKETPLACE_MANIFEST_FILE",
|
|
42
|
-
# Marketplace classes
|
|
43
37
|
"Marketplace",
|
|
44
38
|
"MarketplaceEntry",
|
|
39
|
+
"MarketplaceMetadata",
|
|
45
40
|
"MarketplaceOwner",
|
|
46
41
|
"MarketplacePluginEntry",
|
|
47
42
|
"MarketplacePluginSource",
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
}
|
|
44
|
+
_REGISTRY_EXPORTS = {
|
|
45
|
+
"AmbiguousPluginError",
|
|
46
|
+
"FetchedMarketplace",
|
|
47
|
+
"MarketplaceNotFoundError",
|
|
48
|
+
"MarketplaceRegistry",
|
|
49
|
+
"PluginNotFoundError",
|
|
50
|
+
"PluginResolutionError",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
__all__ = sorted(_TYPE_EXPORTS | _REGISTRY_EXPORTS | {"MarketplaceRegistration"})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def __getattr__(name: str) -> Any:
|
|
58
|
+
if name in _TYPE_EXPORTS:
|
|
59
|
+
return getattr(import_module("openhands.sdk.marketplace.types"), name)
|
|
60
|
+
if name in _REGISTRY_EXPORTS:
|
|
61
|
+
return getattr(import_module("openhands.sdk.marketplace.registry"), name)
|
|
62
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Marketplace registration model."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import PurePosixPath, PureWindowsPath
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field, field_validator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MarketplaceRegistration(BaseModel):
|
|
11
|
+
"""Registration for a marketplace source used for plugin resolution."""
|
|
12
|
+
|
|
13
|
+
name: str = Field(description="Identifier for this marketplace registration")
|
|
14
|
+
source: str = Field(
|
|
15
|
+
description="Marketplace source: 'github:owner/repo', git URL, or local path"
|
|
16
|
+
)
|
|
17
|
+
ref: str | None = Field(
|
|
18
|
+
default=None,
|
|
19
|
+
description="Optional branch, tag, or commit for git sources",
|
|
20
|
+
)
|
|
21
|
+
repo_path: str | None = Field(
|
|
22
|
+
default=None,
|
|
23
|
+
description=(
|
|
24
|
+
"Subdirectory path within the git repository containing the marketplace. "
|
|
25
|
+
"Only relevant for git sources."
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
auto_load: bool = Field(
|
|
29
|
+
default=False,
|
|
30
|
+
description="Whether to load all marketplace plugins at conversation start.",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@field_validator("repo_path")
|
|
34
|
+
@classmethod
|
|
35
|
+
def _validate_repo_path(cls, value: str | None) -> str | None:
|
|
36
|
+
if value is None:
|
|
37
|
+
return None
|
|
38
|
+
if not value:
|
|
39
|
+
raise ValueError("repo_path must not be empty")
|
|
40
|
+
if "\\" in value:
|
|
41
|
+
raise ValueError("repo_path must use '/' separators")
|
|
42
|
+
path = PurePosixPath(value)
|
|
43
|
+
if PureWindowsPath(value).drive or path.is_absolute():
|
|
44
|
+
raise ValueError("repo_path must be relative, not absolute")
|
|
45
|
+
if ".." in path.parts:
|
|
46
|
+
raise ValueError("repo_path cannot contain '..' path traversal")
|
|
47
|
+
return value
|