openhands-sdk 1.43.0__tar.gz → 1.44.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/PKG-INFO +1 -1
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/utils.py +7 -3
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +14 -3
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/sections/static.py +1 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/impl/local_conversation.py +8 -18
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/io/local.py +3 -2
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/llm.py +18 -6
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/settings/model.py +1 -1
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/__init__.py +4 -3
- openhands_sdk-1.44.0/openhands/sdk/utils/async_executor.py +233 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/remote/base.py +45 -26
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/repo.py +26 -12
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands_sdk.egg-info/PKG-INFO +1 -1
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/pyproject.toml +1 -1
- openhands_sdk-1.43.0/openhands/sdk/utils/async_executor.py +0 -127
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/acp_agent.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/acp_file_credentials.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/acp_tracing.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/agent.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/response_dispatch.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/memory.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/presets.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/conversation.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/impl/remote_conversation.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/request.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/secret_registry.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/state.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/credential.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/acp_tool_call.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/error_classification.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/hook_execution.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/fetch.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/manager.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/git_changes.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/git_commits.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/auth/openai.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/exceptions/classifier.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/llm_profile_store.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/model_features.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/marketplace/registration.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/marketplace/registry.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/config.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/discovery.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/fetch.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/format/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/format/base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/agent_profile.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/profile_refs.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/resolver.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/seed.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/settings/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/settings/acp_providers.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/settings/api_models.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/installed.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/skill.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/subagent/load.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/subagent/registry.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/subagent/schema.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/defaults.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/files.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/redact.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands_sdk.egg-info/SOURCES.txt +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands_sdk.egg-info/requires.txt +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.44.0
|
|
4
4
|
Summary: OpenHands SDK - Core functionality for building AI agents
|
|
5
5
|
Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
|
|
6
6
|
Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
|
|
@@ -522,10 +522,14 @@ def normalize_tool_call(
|
|
|
522
522
|
for key, value in arguments.items()
|
|
523
523
|
if key in {"security_risk", "summary"}
|
|
524
524
|
}
|
|
525
|
-
if original_command:
|
|
526
|
-
normalized_arguments["command"] = f"{base_name} {original_command}"
|
|
527
|
-
else:
|
|
525
|
+
if not original_command:
|
|
528
526
|
normalized_arguments["command"] = base_name
|
|
527
|
+
elif str(original_command).split(maxsplit=1)[:1] == [base_name]:
|
|
528
|
+
# Already carries the executable, e.g. git(command="git status").
|
|
529
|
+
# Comparing whole tokens keeps "github status" prefixed.
|
|
530
|
+
normalized_arguments["command"] = original_command
|
|
531
|
+
else:
|
|
532
|
+
normalized_arguments["command"] = f"{base_name} {original_command}"
|
|
529
533
|
elif "terminal" in available_tools:
|
|
530
534
|
terminal_command = _maybe_rewrite_as_terminal_command(
|
|
531
535
|
tool_name,
|
|
@@ -86,7 +86,10 @@ class LLMSummarizingCondenser(RollingCondenser):
|
|
|
86
86
|
# streaming LLM requires. Disable streaming once so every summary path
|
|
87
87
|
# is covered. model_copy is non-mutating and shares usage_id/metrics,
|
|
88
88
|
# so summary tokens stay attributed to the conversation.
|
|
89
|
-
|
|
89
|
+
# Providers that require streaming are exempt: the Codex API requires
|
|
90
|
+
# stream=True, and the responses() method drains the stream internally
|
|
91
|
+
# without an on_token callback.
|
|
92
|
+
if self.llm.stream and not self.llm.requires_streaming:
|
|
90
93
|
self.llm = self.llm.model_copy(update={"stream": False})
|
|
91
94
|
return self
|
|
92
95
|
|
|
@@ -222,8 +225,11 @@ class LLMSummarizingCondenser(RollingCondenser):
|
|
|
222
225
|
|
|
223
226
|
# Do not pass extra_body explicitly. The LLM handles forwarding
|
|
224
227
|
# litellm_extra_body only when it is non-empty.
|
|
228
|
+
from openhands.sdk.agent.utils import make_llm_completion
|
|
229
|
+
|
|
225
230
|
try:
|
|
226
|
-
llm_response =
|
|
231
|
+
llm_response = make_llm_completion(
|
|
232
|
+
llm=self.llm,
|
|
227
233
|
messages=messages,
|
|
228
234
|
)
|
|
229
235
|
except Exception as e:
|
|
@@ -417,8 +423,13 @@ class LLMSummarizingCondenser(RollingCondenser):
|
|
|
417
423
|
)
|
|
418
424
|
|
|
419
425
|
messages = [Message(role="user", content=[TextContent(text=prompt)])]
|
|
426
|
+
from openhands.sdk.agent.utils import amake_llm_completion
|
|
427
|
+
|
|
420
428
|
try:
|
|
421
|
-
llm_response = await
|
|
429
|
+
llm_response = await amake_llm_completion(
|
|
430
|
+
llm=self.llm,
|
|
431
|
+
messages=messages,
|
|
432
|
+
)
|
|
422
433
|
except Exception as e:
|
|
423
434
|
raise NoCondensationAvailableException(
|
|
424
435
|
f"Summarization LLM call failed: {e}"
|
{openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/sections/static.py
RENAMED
|
@@ -108,6 +108,7 @@ class MemorySection(_StaticTextSection):
|
|
|
108
108
|
_AGENTS_MD_GUIDANCE = """\
|
|
109
109
|
* Use `AGENTS.md` under the repository root as your persistent memory for repository-specific knowledge and context.
|
|
110
110
|
* Add important insights, patterns, and learnings to this file to improve future task performance.
|
|
111
|
+
* When asked to find a previous local OpenHands conversation, search the workspace's `workspace/conversations/` directory for its event history.
|
|
111
112
|
* This repository skill is automatically loaded for every conversation and helps maintain context across sessions.
|
|
112
113
|
* For more information about skills, see: https://docs.openhands.dev/overview/skills"""
|
|
113
114
|
|
{openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/impl/local_conversation.py
RENAMED
|
@@ -202,7 +202,6 @@ class LocalConversation(BaseConversation):
|
|
|
202
202
|
_resolved_plugins: list[ResolvedPluginSource] | None
|
|
203
203
|
_plugins_loaded: bool
|
|
204
204
|
_pending_hook_config: HookConfig | None # Hook config to combine with plugin hooks
|
|
205
|
-
_subscription_disabled_condenser: Any | None
|
|
206
205
|
_mcp_tool_provider: MCPToolProvider
|
|
207
206
|
|
|
208
207
|
def __init__(
|
|
@@ -238,6 +237,7 @@ class LocalConversation(BaseConversation):
|
|
|
238
237
|
prompt_cache_key: str | None = None,
|
|
239
238
|
file_store: FileStore | None = None,
|
|
240
239
|
mcp_tool_provider: MCPToolProvider | None = None,
|
|
240
|
+
profile_store_dir: str | Path | None = None,
|
|
241
241
|
**_: object,
|
|
242
242
|
):
|
|
243
243
|
"""Initialize the conversation.
|
|
@@ -296,6 +296,8 @@ class LocalConversation(BaseConversation):
|
|
|
296
296
|
file_store: Optional FileStore to use for conversation state and EventLog
|
|
297
297
|
persistence. If provided, this takes precedence over persistence_dir
|
|
298
298
|
for state and EventLog storage.
|
|
299
|
+
profile_store_dir: Optional directory containing saved LLM profiles.
|
|
300
|
+
Defaults to ``~/.openhands/profiles``.
|
|
299
301
|
"""
|
|
300
302
|
super().__init__() # Initialize with span tracking
|
|
301
303
|
# Mark cleanup as initiated as early as possible to avoid races or partially
|
|
@@ -314,7 +316,6 @@ class LocalConversation(BaseConversation):
|
|
|
314
316
|
self._plugins_loaded = False
|
|
315
317
|
self._pending_hook_config = hook_config # Will be combined with plugin hooks
|
|
316
318
|
self._agent_ready = False # Agent initialized lazily after plugins loaded
|
|
317
|
-
self._subscription_disabled_condenser = None
|
|
318
319
|
self._mcp_tool_provider = mcp_tool_provider or DefaultMCPToolProvider()
|
|
319
320
|
|
|
320
321
|
# Create-or-resume: factory inspects BASE_STATE to decide
|
|
@@ -484,7 +485,7 @@ class LocalConversation(BaseConversation):
|
|
|
484
485
|
# Agent initialization is deferred to _ensure_agent_ready() for lazy loading
|
|
485
486
|
# This ensures plugins are loaded before agent initialization
|
|
486
487
|
self.llm_registry = LLMRegistry()
|
|
487
|
-
self._profile_store = LLMProfileStore()
|
|
488
|
+
self._profile_store = LLMProfileStore(profile_store_dir)
|
|
488
489
|
self._cipher = cipher
|
|
489
490
|
|
|
490
491
|
# Seed agent_context.secrets into the registry for every agent (regular
|
|
@@ -1659,21 +1660,10 @@ class LocalConversation(BaseConversation):
|
|
|
1659
1660
|
lock = contextlib.nullcontext() if skip_lock else self._state
|
|
1660
1661
|
with lock:
|
|
1661
1662
|
update: dict[str, object] = {"llm": new_llm}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
elif (
|
|
1667
|
-
self.agent.condenser is None
|
|
1668
|
-
and self._subscription_disabled_condenser is not None
|
|
1669
|
-
):
|
|
1670
|
-
update["condenser"] = self._subscription_disabled_condenser
|
|
1671
|
-
self._subscription_disabled_condenser = None
|
|
1672
|
-
else:
|
|
1673
|
-
update["condenser"] = self._condenser_for_switched_llm(
|
|
1674
|
-
self.agent.llm,
|
|
1675
|
-
new_llm,
|
|
1676
|
-
)
|
|
1663
|
+
update["condenser"] = self._condenser_for_switched_llm(
|
|
1664
|
+
self.agent.llm,
|
|
1665
|
+
new_llm,
|
|
1666
|
+
)
|
|
1677
1667
|
self.agent = self.agent.model_copy(update=update)
|
|
1678
1668
|
self._state.agent = self.agent
|
|
1679
1669
|
self._bind_conversation_context(new_llm)
|
|
@@ -2,11 +2,13 @@ import os
|
|
|
2
2
|
import shutil
|
|
3
3
|
from collections.abc import Iterator
|
|
4
4
|
from contextlib import contextmanager
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
from filelock import FileLock, Timeout
|
|
7
8
|
|
|
8
9
|
from openhands.sdk.io.cache import MemoryLRUCache
|
|
9
10
|
from openhands.sdk.logger import get_logger
|
|
11
|
+
from openhands.sdk.utils.files import atomic_write_text
|
|
10
12
|
from openhands.sdk.utils.path import to_posix_path
|
|
11
13
|
|
|
12
14
|
from .base import FileStore
|
|
@@ -62,8 +64,7 @@ class LocalFileStore(FileStore):
|
|
|
62
64
|
full_path = self.get_full_path(path)
|
|
63
65
|
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
|
64
66
|
if isinstance(contents, str):
|
|
65
|
-
|
|
66
|
-
f.write(contents)
|
|
67
|
+
atomic_write_text(Path(full_path), contents)
|
|
67
68
|
self.cache[full_path] = contents
|
|
68
69
|
else:
|
|
69
70
|
with open(full_path, "wb") as f:
|
|
@@ -924,6 +924,16 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
924
924
|
def is_subscription(self, value: bool) -> None:
|
|
925
925
|
self._is_subscription = value
|
|
926
926
|
|
|
927
|
+
@property
|
|
928
|
+
def requires_streaming(self) -> bool:
|
|
929
|
+
"""Whether the provider requires stream=True for all requests.
|
|
930
|
+
|
|
931
|
+
Set when the underlying endpoint rejects non-streaming requests;
|
|
932
|
+
callers must leave streaming enabled and must not require an
|
|
933
|
+
on_token callback because the response is drained internally.
|
|
934
|
+
"""
|
|
935
|
+
return self._is_subscription
|
|
936
|
+
|
|
927
937
|
@model_validator(mode="wrap")
|
|
928
938
|
@classmethod
|
|
929
939
|
def _restore_is_subscription(cls, data, handler):
|
|
@@ -1765,10 +1775,11 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
1765
1775
|
|
|
1766
1776
|
_caller_kwargs = kwargs.copy()
|
|
1767
1777
|
user_enable_streaming = bool(kwargs.get("stream", False)) or self.stream
|
|
1768
|
-
if user_enable_streaming and on_token is None and not self.
|
|
1778
|
+
if user_enable_streaming and on_token is None and not self.requires_streaming:
|
|
1769
1779
|
# Gracefully degrade to non-streaming rather than crashing a run when
|
|
1770
|
-
# streaming is requested without a callback wired
|
|
1771
|
-
#
|
|
1780
|
+
# streaming is requested without a callback wired. Providers that
|
|
1781
|
+
# require streaming are exempt — they drain the stream internally
|
|
1782
|
+
# without an on_token callback. See requires_streaming and #4014.
|
|
1772
1783
|
logger.debug(
|
|
1773
1784
|
"Streaming requested without an on_token callback; "
|
|
1774
1785
|
"falling back to a non-streaming responses call."
|
|
@@ -1915,10 +1926,11 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
1915
1926
|
|
|
1916
1927
|
_caller_kwargs = kwargs.copy()
|
|
1917
1928
|
user_enable_streaming = bool(kwargs.get("stream", False)) or self.stream
|
|
1918
|
-
if user_enable_streaming and on_token is None and not self.
|
|
1929
|
+
if user_enable_streaming and on_token is None and not self.requires_streaming:
|
|
1919
1930
|
# Gracefully degrade to non-streaming rather than crashing a run when
|
|
1920
|
-
# streaming is requested without a callback wired
|
|
1921
|
-
#
|
|
1931
|
+
# streaming is requested without a callback wired. Providers that
|
|
1932
|
+
# require streaming are exempt — they drain the stream internally
|
|
1933
|
+
# without an on_token callback. See requires_streaming and #4014.
|
|
1922
1934
|
logger.debug(
|
|
1923
1935
|
"Streaming requested without an on_token callback; "
|
|
1924
1936
|
"falling back to a non-streaming responses call."
|
|
@@ -1389,7 +1389,7 @@ class OpenHandsAgentSettings(AgentSettingsBase):
|
|
|
1389
1389
|
include_default_tools.append(SwitchLLMTool.__name__)
|
|
1390
1390
|
|
|
1391
1391
|
llm = create_subscription_llm_from_config(self.llm)
|
|
1392
|
-
condenser =
|
|
1392
|
+
condenser = self.build_condenser(llm)
|
|
1393
1393
|
return Agent(
|
|
1394
1394
|
llm=llm,
|
|
1395
1395
|
tools=tools,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""SDK-resident tools that do not interact with the environment.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`BUILT_IN_TOOLS` contains tools attached to every agent. `BUILT_IN_TOOL_CLASSES`
|
|
4
|
+
also includes optional SDK tools that are resolved by name from agent setup.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Tools that require interacting with the environment belong in `openhands-tools`.
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
from openhands.sdk.tool.builtins.finish import (
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import inspect
|
|
3
|
+
import threading
|
|
4
|
+
import weakref
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import anyio
|
|
9
|
+
from anyio.from_thread import BlockingPortal, start_blocking_portal
|
|
10
|
+
|
|
11
|
+
from openhands.sdk.logger import get_logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
# Upper bound on how long close() waits for the portal thread to wind down.
|
|
17
|
+
# The portal thread is a daemon thread, so abandoning it is safe; blocking the
|
|
18
|
+
# caller forever is not.
|
|
19
|
+
#
|
|
20
|
+
# The default is intentionally short for lifecycle teardown: a successful
|
|
21
|
+
# portal stop + join normally completes in milliseconds once remaining tasks
|
|
22
|
+
# are cancelled, so 10s is already a generous margin for a healthy shutdown.
|
|
23
|
+
# It is deliberately *not* the 30s inherited from BrowserToolExecutor cleanup
|
|
24
|
+
# — that value predates the lifecycle-lock path and is too long for teardown
|
|
25
|
+
# that may hold a conversation lock while waiting. See PR #4548 and the
|
|
26
|
+
# discussion of #4598 in its description.
|
|
27
|
+
DEFAULT_CLOSE_TIMEOUT = 10.0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AsyncExecutor:
|
|
31
|
+
"""
|
|
32
|
+
Thin wrapper around AnyIO's BlockingPortal to execute async code
|
|
33
|
+
from synchronous contexts with proper resource and timeout handling.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self._portal = None
|
|
38
|
+
self._portal_cm = None
|
|
39
|
+
self._lock = threading.Lock()
|
|
40
|
+
self._atexit_registered = False
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def portal(self) -> BlockingPortal:
|
|
44
|
+
"""The lazily-started ``BlockingPortal`` — public accessor.
|
|
45
|
+
|
|
46
|
+
Callers that need to schedule work directly on the portal loop
|
|
47
|
+
(e.g. ``ACPAgent.astep`` bridges ACP awaits across loops via
|
|
48
|
+
``portal.start_task_soon`` + ``asyncio.wrap_future``) use this
|
|
49
|
+
instead of ``run_async`` because they need the future, not the
|
|
50
|
+
result.
|
|
51
|
+
"""
|
|
52
|
+
return self._ensure_portal()
|
|
53
|
+
|
|
54
|
+
def _ensure_portal(self) -> BlockingPortal:
|
|
55
|
+
with self._lock:
|
|
56
|
+
if self._portal is None:
|
|
57
|
+
self._portal_cm = start_blocking_portal()
|
|
58
|
+
self._portal = self._portal_cm.__enter__()
|
|
59
|
+
# Register atexit handler to ensure cleanup on interpreter shutdown
|
|
60
|
+
if not self._atexit_registered:
|
|
61
|
+
# Use weakref to avoid keeping the executor alive
|
|
62
|
+
weak_self = weakref.ref(self)
|
|
63
|
+
|
|
64
|
+
def cleanup():
|
|
65
|
+
executor = weak_self()
|
|
66
|
+
if executor is not None:
|
|
67
|
+
try:
|
|
68
|
+
executor.close()
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
atexit.register(cleanup)
|
|
73
|
+
self._atexit_registered = True
|
|
74
|
+
return self._portal
|
|
75
|
+
|
|
76
|
+
def run_async(
|
|
77
|
+
self,
|
|
78
|
+
awaitable_or_fn: Callable[..., Any] | Any,
|
|
79
|
+
*args,
|
|
80
|
+
timeout: float | None = None,
|
|
81
|
+
**kwargs,
|
|
82
|
+
) -> Any:
|
|
83
|
+
"""
|
|
84
|
+
Run a coroutine or async function from sync code.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
awaitable_or_fn: coroutine or async function
|
|
88
|
+
*args: positional arguments (only used if awaitable_or_fn is a function)
|
|
89
|
+
timeout: optional timeout in seconds
|
|
90
|
+
**kwargs: keyword arguments (only used if awaitable_or_fn is a function)
|
|
91
|
+
"""
|
|
92
|
+
portal = self._ensure_portal()
|
|
93
|
+
|
|
94
|
+
# Construct coroutine
|
|
95
|
+
if inspect.iscoroutine(awaitable_or_fn):
|
|
96
|
+
coro = awaitable_or_fn
|
|
97
|
+
elif inspect.iscoroutinefunction(awaitable_or_fn):
|
|
98
|
+
coro = awaitable_or_fn(*args, **kwargs)
|
|
99
|
+
else:
|
|
100
|
+
raise TypeError("run_async expects a coroutine or async function")
|
|
101
|
+
|
|
102
|
+
# Apply timeout by wrapping in an async function with fail_after
|
|
103
|
+
if timeout is not None:
|
|
104
|
+
|
|
105
|
+
async def _with_timeout():
|
|
106
|
+
with anyio.fail_after(timeout):
|
|
107
|
+
return await coro
|
|
108
|
+
|
|
109
|
+
return portal.call(_with_timeout)
|
|
110
|
+
else:
|
|
111
|
+
|
|
112
|
+
async def _execute():
|
|
113
|
+
return await coro
|
|
114
|
+
|
|
115
|
+
return portal.call(_execute)
|
|
116
|
+
|
|
117
|
+
def close(self, timeout: float | None = DEFAULT_CLOSE_TIMEOUT):
|
|
118
|
+
"""Shut down the portal, without ever blocking the caller forever.
|
|
119
|
+
|
|
120
|
+
Semantics
|
|
121
|
+
---------
|
|
122
|
+
This is **bounded, best-effort shutdown, not guaranteed cleanup.**
|
|
123
|
+
|
|
124
|
+
- It first cancels any remaining portal tasks (``portal.stop(True)``)
|
|
125
|
+
and then waits up to ``timeout`` seconds for the portal thread to
|
|
126
|
+
exit.
|
|
127
|
+
- If the portal thread is stuck on work that does *not* honour
|
|
128
|
+
cancellation — for example a task awaiting inside a worker thread,
|
|
129
|
+
which anyio cannot interrupt until the thread returns (see #4598) —
|
|
130
|
+
the wait expires and the helper thread is **abandoned**. The portal
|
|
131
|
+
thread is a daemon, so the process can still exit; but the thread
|
|
132
|
+
and any resources it holds (subprocess handles, sockets, file
|
|
133
|
+
descriptors) may remain alive until process exit. This is a
|
|
134
|
+
deliberate trade-off: blocking the caller forever is worse.
|
|
135
|
+
- The shutdown path is non-raising. Failures inside the portal
|
|
136
|
+
teardown are logged (with traceback) and swallowed so that
|
|
137
|
+
``close()`` is always safe to call from a destructor or
|
|
138
|
+
``__exit__``.
|
|
139
|
+
- **Idempotent.** Calling ``close()`` on an already-closed executor
|
|
140
|
+
is a no-op.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
timeout: seconds to wait for the portal thread to exit. ``None``
|
|
144
|
+
waits indefinitely (the previous, pre-#4548 behaviour) and
|
|
145
|
+
is kept only for compatibility — **do not use it on any
|
|
146
|
+
production path**, since it reintroduces the unbounded hang
|
|
147
|
+
this fix exists to prevent. See PR #4548 / issue #4598.
|
|
148
|
+
"""
|
|
149
|
+
with self._lock:
|
|
150
|
+
portal_cm = self._portal_cm
|
|
151
|
+
portal = self._portal
|
|
152
|
+
self._portal_cm = None
|
|
153
|
+
self._portal = None
|
|
154
|
+
|
|
155
|
+
if portal_cm is None:
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
# Stamp the owner into the thread name so an abandoned thread is
|
|
159
|
+
# identifiable in py-spy / py-dump traces (the portal thread itself
|
|
160
|
+
# does not carry the executor identity).
|
|
161
|
+
owner = type(self).__qualname__
|
|
162
|
+
thread_name = f"{owner}-close"
|
|
163
|
+
|
|
164
|
+
def _shutdown() -> None:
|
|
165
|
+
try:
|
|
166
|
+
# Cancel whatever is still running. Without this, anyio's
|
|
167
|
+
# graceful path (portal.stop(cancel_remaining=False)) waits
|
|
168
|
+
# for in-flight tasks that may never complete on their own.
|
|
169
|
+
if portal is not None:
|
|
170
|
+
portal.call(portal.stop, True)
|
|
171
|
+
except RuntimeError:
|
|
172
|
+
pass # portal already stopped
|
|
173
|
+
except Exception:
|
|
174
|
+
# Teardown must stay non-raising: close() is called from
|
|
175
|
+
# __del__/__exit__/atexit. Log with traceback so the failure
|
|
176
|
+
# remains diagnosable instead of being reduced to str(exc).
|
|
177
|
+
logger.warning(
|
|
178
|
+
"Error stopping BlockingPortal during AsyncExecutor.close "
|
|
179
|
+
"(owner=%s); teardown continues.",
|
|
180
|
+
owner,
|
|
181
|
+
exc_info=True,
|
|
182
|
+
)
|
|
183
|
+
try:
|
|
184
|
+
portal_cm.__exit__(None, None, None)
|
|
185
|
+
except Exception:
|
|
186
|
+
logger.warning(
|
|
187
|
+
"Error closing BlockingPortal context manager during "
|
|
188
|
+
"AsyncExecutor.close (owner=%s); teardown continues.",
|
|
189
|
+
owner,
|
|
190
|
+
exc_info=True,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Run the shutdown on a helper thread so we can bound the wait: the
|
|
194
|
+
# portal thread can be stuck on work that does not honour cancellation
|
|
195
|
+
# (for example an await blocked inside a worker thread), and anyio
|
|
196
|
+
# joins it with no timeout.
|
|
197
|
+
try:
|
|
198
|
+
waiter = threading.Thread(target=_shutdown, name=thread_name, daemon=True)
|
|
199
|
+
waiter.start()
|
|
200
|
+
except RuntimeError:
|
|
201
|
+
# Interpreter is shutting down and will not start new threads.
|
|
202
|
+
# The portal thread is a daemon; let the process reap it.
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
waiter.join(timeout)
|
|
206
|
+
if waiter.is_alive():
|
|
207
|
+
# Abandonment is observable: name the owner, the timeout, and
|
|
208
|
+
# that cancellation was already attempted, so the operator can
|
|
209
|
+
# correlate with py-spy / the wedged resource. Per #4598 the
|
|
210
|
+
# thread may genuinely be un-interruptible from Python.
|
|
211
|
+
logger.warning(
|
|
212
|
+
"AsyncExecutor (owner=%s): BlockingPortal did not shut down "
|
|
213
|
+
"within %.1fs; abandoning its helper thread. Cancellation was "
|
|
214
|
+
"attempted but the portal thread appears to be stuck on work "
|
|
215
|
+
"that does not honour cancellation. Its resources (subprocess "
|
|
216
|
+
"handles, sockets, fds) may remain alive until process exit. "
|
|
217
|
+
"See issue #4598.",
|
|
218
|
+
owner,
|
|
219
|
+
float(timeout) if timeout is not None else -1.0,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def __enter__(self):
|
|
223
|
+
return self
|
|
224
|
+
|
|
225
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
226
|
+
self.close()
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
def __del__(self):
|
|
230
|
+
try:
|
|
231
|
+
self.close()
|
|
232
|
+
except Exception:
|
|
233
|
+
pass
|
|
@@ -321,27 +321,30 @@ class RemoteWorkspace(RemoteWorkspaceMixin, BaseWorkspace):
|
|
|
321
321
|
# settings endpoints. Subclasses like OpenHandsCloudWorkspace may override
|
|
322
322
|
# to use alternative endpoints (e.g., Cloud API).
|
|
323
323
|
|
|
324
|
+
def _fetch_settings_response(
|
|
325
|
+
self, *, expose_secrets: bool = True
|
|
326
|
+
) -> SettingsResponse:
|
|
327
|
+
"""Call ``GET /api/settings`` and return the validated response."""
|
|
328
|
+
headers = dict(self._headers)
|
|
329
|
+
if expose_secrets:
|
|
330
|
+
headers["X-Expose-Secrets"] = "plaintext"
|
|
331
|
+
|
|
332
|
+
response = self.client.get("/api/settings", headers=headers)
|
|
333
|
+
response.raise_for_status()
|
|
334
|
+
return SettingsResponse.model_validate(response.json())
|
|
335
|
+
|
|
324
336
|
def _fetch_agent_settings(
|
|
325
337
|
self,
|
|
326
338
|
) -> "OpenHandsAgentSettings | LLMAgentSettings | ACPAgentSettings":
|
|
327
|
-
"""
|
|
339
|
+
"""Return the validated agent settings from ``GET /api/settings``.
|
|
328
340
|
|
|
329
341
|
Uses ``X-Expose-Secrets: plaintext`` so secret fields (e.g. LLM
|
|
330
|
-
api_key) are returned as plain strings.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
picking the correct discriminated-union variant
|
|
335
|
-
(``OpenHandsAgentSettings`` or ``ACPAgentSettings``).
|
|
342
|
+
api_key) are returned as plain strings. The validated
|
|
343
|
+
``SettingsResponse`` is narrowed through
|
|
344
|
+
:meth:`SettingsResponse.get_agent_settings`, which selects the correct
|
|
345
|
+
discriminated-union variant.
|
|
336
346
|
"""
|
|
337
|
-
|
|
338
|
-
headers["X-Expose-Secrets"] = "plaintext"
|
|
339
|
-
|
|
340
|
-
response = self.client.get("/api/settings", headers=headers)
|
|
341
|
-
response.raise_for_status()
|
|
342
|
-
|
|
343
|
-
data = SettingsResponse.model_validate(response.json())
|
|
344
|
-
return data.get_agent_settings()
|
|
347
|
+
return self._fetch_settings_response().get_agent_settings()
|
|
345
348
|
|
|
346
349
|
def _fetch_llm_profile_config(self, profile_name: str) -> dict[str, Any]:
|
|
347
350
|
"""Call ``GET /api/profiles/{name}`` and return plaintext LLM config."""
|
|
@@ -368,16 +371,23 @@ class RemoteWorkspace(RemoteWorkspaceMixin, BaseWorkspace):
|
|
|
368
371
|
reraise=True,
|
|
369
372
|
)
|
|
370
373
|
def get_llm(self, profile_name: str | None = None, **llm_kwargs: Any) -> "LLM":
|
|
371
|
-
"""Fetch
|
|
374
|
+
"""Fetch the active or explicitly named LLM profile.
|
|
375
|
+
|
|
376
|
+
When no ``profile_name`` is given, the persisted ``active_profile``
|
|
377
|
+
pointer is resolved first (so the UI-advertised default is honored).
|
|
378
|
+
If no ``active_profile`` is configured, the legacy
|
|
379
|
+
``agent_settings.llm`` payload is used as a fallback (preserving
|
|
380
|
+
backward compatibility for servers that have not adopted named
|
|
381
|
+
profiles).
|
|
372
382
|
|
|
373
383
|
Args:
|
|
374
384
|
profile_name: Optional LLM profile name. When provided, loads that
|
|
375
|
-
named profile instead of the active
|
|
376
|
-
**llm_kwargs: Additional keyword arguments that override
|
|
377
|
-
|
|
385
|
+
named profile instead of resolving the active profile.
|
|
386
|
+
**llm_kwargs: Additional keyword arguments that override profile
|
|
387
|
+
values (e.g., ``model``, ``temperature``).
|
|
378
388
|
|
|
379
389
|
Returns:
|
|
380
|
-
An LLM instance configured with the
|
|
390
|
+
An LLM instance configured with the active or named profile.
|
|
381
391
|
|
|
382
392
|
Raises:
|
|
383
393
|
FileNotFoundError: If ``profile_name`` does not exist.
|
|
@@ -394,14 +404,23 @@ class RemoteWorkspace(RemoteWorkspaceMixin, BaseWorkspace):
|
|
|
394
404
|
if not self.host or self.host == "undefined":
|
|
395
405
|
raise RuntimeError("Workspace host is not set")
|
|
396
406
|
|
|
397
|
-
if profile_name:
|
|
407
|
+
if profile_name is None:
|
|
408
|
+
settings_response = self._fetch_settings_response(expose_secrets=False)
|
|
409
|
+
resolved_profile_name = settings_response.active_profile
|
|
410
|
+
if resolved_profile_name in (None, ""):
|
|
411
|
+
settings_response = self._fetch_settings_response()
|
|
412
|
+
agent_settings = settings_response.get_agent_settings()
|
|
413
|
+
if not llm_kwargs:
|
|
414
|
+
return agent_settings.llm
|
|
415
|
+
llm_data = agent_settings.llm.model_dump(
|
|
416
|
+
context={"expose_secrets": "plaintext"}
|
|
417
|
+
)
|
|
418
|
+
else:
|
|
419
|
+
llm_data = self._fetch_llm_profile_config(resolved_profile_name)
|
|
420
|
+
llm_data["usage_id"] = f"profile:{resolved_profile_name}"
|
|
421
|
+
else:
|
|
398
422
|
llm_data = self._fetch_llm_profile_config(profile_name)
|
|
399
423
|
llm_data["usage_id"] = f"profile:{profile_name}"
|
|
400
|
-
else:
|
|
401
|
-
settings = self._fetch_agent_settings()
|
|
402
|
-
if not llm_kwargs:
|
|
403
|
-
return settings.llm
|
|
404
|
-
llm_data = settings.llm.model_dump(context={"expose_secrets": "plaintext"})
|
|
405
424
|
|
|
406
425
|
llm_data.update(llm_kwargs)
|
|
407
426
|
return LLM(**llm_data)
|
|
@@ -294,10 +294,17 @@ _PROVIDER_CONFIG: dict[GitProvider, tuple[str, str]] = {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
|
|
297
|
-
def _build_clone_url(
|
|
297
|
+
def _build_clone_url(
|
|
298
|
+
url: str,
|
|
299
|
+
provider: GitProvider,
|
|
300
|
+
token: str | None,
|
|
301
|
+
*,
|
|
302
|
+
explicit_provider: bool = False,
|
|
303
|
+
) -> str:
|
|
298
304
|
"""Build authenticated clone URL based on the repository URL and provider.
|
|
299
305
|
|
|
300
|
-
|
|
306
|
+
The token is injected into the provider's public host, or into the URL's own
|
|
307
|
+
host when the caller set `provider` explicitly (self-hosted instances).
|
|
301
308
|
"""
|
|
302
309
|
config = _PROVIDER_CONFIG.get(provider)
|
|
303
310
|
if not config:
|
|
@@ -311,16 +318,21 @@ def _build_clone_url(url: str, provider: GitProvider, token: str | None) -> str:
|
|
|
311
318
|
if is_short_format:
|
|
312
319
|
return f"https://{auth_prefix}{base_url}/{url}.git"
|
|
313
320
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
parsed = urllib.parse.urlparse(url)
|
|
317
|
-
if parsed.netloc.lower() == base_url:
|
|
318
|
-
# Replace only the first occurrence to prevent double injection
|
|
319
|
-
return url.replace(
|
|
320
|
-
f"https://{base_url}", f"https://{auth_prefix}{base_url}", 1
|
|
321
|
-
)
|
|
321
|
+
if not token:
|
|
322
|
+
return url
|
|
322
323
|
|
|
323
|
-
|
|
324
|
+
parsed = urllib.parse.urlparse(url)
|
|
325
|
+
hostname = (parsed.hostname or "").lower()
|
|
326
|
+
if parsed.scheme != "https" or parsed.username or not hostname:
|
|
327
|
+
return url
|
|
328
|
+
if hostname != base_url:
|
|
329
|
+
# An auto-detected provider is derived from the URL, so it cannot
|
|
330
|
+
# authorize another host - and never a lookalike of the public one.
|
|
331
|
+
if not explicit_provider or hostname.startswith(f"{base_url}."):
|
|
332
|
+
return url
|
|
333
|
+
|
|
334
|
+
netloc = hostname if parsed.port is None else f"{hostname}:{parsed.port}"
|
|
335
|
+
return urllib.parse.urlunparse(parsed._replace(netloc=f"{auth_prefix}{netloc}"))
|
|
324
336
|
|
|
325
337
|
|
|
326
338
|
# Type for functions that fetch tokens by name (e.g., "github_token" -> token value)
|
|
@@ -367,7 +379,9 @@ def _clone_single_repo(repo: RepoSource, dest: Path, token: str | None) -> bool:
|
|
|
367
379
|
"""Clone a single repository. Returns True on success."""
|
|
368
380
|
try:
|
|
369
381
|
provider = repo.get_provider()
|
|
370
|
-
clone_url = _build_clone_url(
|
|
382
|
+
clone_url = _build_clone_url(
|
|
383
|
+
repo.url, provider, token, explicit_provider=repo.provider is not None
|
|
384
|
+
)
|
|
371
385
|
provider_str = provider.value
|
|
372
386
|
except ValueError:
|
|
373
387
|
# No provider detected (e.g., file:// URLs) - use URL as-is
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.44.0
|
|
4
4
|
Summary: OpenHands SDK - Core functionality for building AI agents
|
|
5
5
|
Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
|
|
6
6
|
Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
|