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.
Files changed (302) hide show
  1. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/PKG-INFO +1 -1
  2. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/utils.py +7 -3
  3. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +14 -3
  4. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/sections/static.py +1 -0
  5. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/impl/local_conversation.py +8 -18
  6. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/io/local.py +3 -2
  7. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/llm.py +18 -6
  8. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/settings/model.py +1 -1
  9. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/__init__.py +4 -3
  10. openhands_sdk-1.44.0/openhands/sdk/utils/async_executor.py +233 -0
  11. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/remote/base.py +45 -26
  12. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/repo.py +26 -12
  13. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands_sdk.egg-info/PKG-INFO +1 -1
  14. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/pyproject.toml +1 -1
  15. openhands_sdk-1.43.0/openhands/sdk/utils/async_executor.py +0 -127
  16. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/__init__.py +0 -0
  17. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/__init__.py +0 -0
  18. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/acp_agent.py +0 -0
  19. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/acp_file_credentials.py +0 -0
  20. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/acp_models.py +0 -0
  21. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/acp_tracing.py +0 -0
  22. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/agent.py +0 -0
  23. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/base.py +0 -0
  24. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/critic_mixin.py +0 -0
  25. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/parallel_executor.py +0 -0
  26. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/agent/response_dispatch.py +0 -0
  27. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/banner.py +0 -0
  28. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/__init__.py +0 -0
  29. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/agent_context.py +0 -0
  30. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/__init__.py +0 -0
  31. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/base.py +0 -0
  32. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  33. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  34. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
  35. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/condenser/utils.py +0 -0
  36. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/memory.py +0 -0
  37. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/__init__.py +0 -0
  38. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/presets.py +0 -0
  39. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/prompt.py +0 -0
  40. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/registry.py +0 -0
  41. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/section.py +0 -0
  42. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
  43. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
  44. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
  45. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  46. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  47. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/__init__.py +0 -0
  48. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
  49. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
  50. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/base.py +0 -0
  51. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
  52. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
  53. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
  54. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
  55. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/context/view/view.py +0 -0
  56. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/__init__.py +0 -0
  57. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/base.py +0 -0
  58. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/cancellation.py +0 -0
  59. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/conversation.py +0 -0
  60. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
  61. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/event_store.py +0 -0
  62. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/events_list_base.py +0 -0
  63. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/exceptions.py +0 -0
  64. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
  65. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
  66. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/goal/controller.py +0 -0
  67. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/goal/judge.py +0 -0
  68. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
  69. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/goal/runner.py +0 -0
  70. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
  71. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/impl/remote_conversation.py +0 -0
  72. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/persistence_const.py +0 -0
  73. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/request.py +0 -0
  74. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
  75. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/response_utils.py +0 -0
  76. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/secret_registry.py +0 -0
  77. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/state.py +0 -0
  78. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
  79. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/title_utils.py +0 -0
  80. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/types.py +0 -0
  81. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  82. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
  83. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
  84. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/credential.py +0 -0
  85. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/__init__.py +0 -0
  86. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/base.py +0 -0
  87. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/__init__.py +0 -0
  88. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  89. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  90. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  91. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/api/client.py +0 -0
  92. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
  93. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  94. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  95. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  96. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/critic/result.py +0 -0
  97. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/__init__.py +0 -0
  98. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/acp_tool_call.py +0 -0
  99. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/base.py +0 -0
  100. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/condenser.py +0 -0
  101. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/conversation_error.py +0 -0
  102. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/conversation_state.py +0 -0
  103. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/error_classification.py +0 -0
  104. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/hook_execution.py +0 -0
  105. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_completion_log.py +0 -0
  106. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  107. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
  108. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
  109. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  110. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
  111. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
  112. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/resume_transcript.py +0 -0
  113. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/streaming_delta.py +0 -0
  114. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/token.py +0 -0
  115. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/types.py +0 -0
  116. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/event/user_action.py +0 -0
  117. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/__init__.py +0 -0
  118. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/fetch.py +0 -0
  119. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
  120. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/info.py +0 -0
  121. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/interface.py +0 -0
  122. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/manager.py +0 -0
  123. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
  124. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/extensions/installation/utils.py +0 -0
  125. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/cached_repo.py +0 -0
  126. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/exceptions.py +0 -0
  127. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/git_changes.py +0 -0
  128. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/git_commits.py +0 -0
  129. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/git_diff.py +0 -0
  130. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/models.py +0 -0
  131. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/git/utils.py +0 -0
  132. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/__init__.py +0 -0
  133. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/config.py +0 -0
  134. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  135. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/executor.py +0 -0
  136. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/manager.py +0 -0
  137. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/hooks/types.py +0 -0
  138. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/io/__init__.py +0 -0
  139. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/io/base.py +0 -0
  140. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/io/cache.py +0 -0
  141. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/io/memory.py +0 -0
  142. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/__init__.py +0 -0
  143. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/auth/__init__.py +0 -0
  144. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/auth/credentials.py +0 -0
  145. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/auth/openai.py +0 -0
  146. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
  147. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
  148. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/exceptions/classifier.py +0 -0
  149. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
  150. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/exceptions/types.py +0 -0
  151. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
  152. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/llm_profile_store.py +0 -0
  153. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/llm_registry.py +0 -0
  154. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/llm_response.py +0 -0
  155. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/message.py +0 -0
  156. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  157. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
  158. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  159. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/options/__init__.py +0 -0
  160. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/options/chat_options.py +0 -0
  161. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/options/common.py +0 -0
  162. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/options/responses_options.py +0 -0
  163. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
  164. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/router/__init__.py +0 -0
  165. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/router/base.py +0 -0
  166. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  167. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/router/impl/random.py +0 -0
  168. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/streaming.py +0 -0
  169. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
  170. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
  171. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
  172. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/metrics.py +0 -0
  173. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/model_features.py +0 -0
  174. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/model_info.py +0 -0
  175. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  176. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
  177. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
  178. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
  179. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
  180. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
  181. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
  182. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
  183. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  184. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
  185. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
  186. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/logger/__init__.py +0 -0
  187. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/logger/logger.py +0 -0
  188. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/logger/rolling.py +0 -0
  189. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/marketplace/__init__.py +0 -0
  190. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/marketplace/registration.py +0 -0
  191. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/marketplace/registry.py +0 -0
  192. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/marketplace/types.py +0 -0
  193. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/__init__.py +0 -0
  194. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/client.py +0 -0
  195. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/config.py +0 -0
  196. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/definition.py +0 -0
  197. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/exceptions.py +0 -0
  198. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/tool.py +0 -0
  199. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/mcp/utils.py +0 -0
  200. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/observability/__init__.py +0 -0
  201. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/observability/laminar.py +0 -0
  202. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/observability/utils.py +0 -0
  203. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/__init__.py +0 -0
  204. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/discovery.py +0 -0
  205. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/fetch.py +0 -0
  206. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/format/__init__.py +0 -0
  207. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
  208. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/format/base.py +0 -0
  209. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
  210. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
  211. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/installed.py +0 -0
  212. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/loader.py +0 -0
  213. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/plugin.py +0 -0
  214. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/source.py +0 -0
  215. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/plugin/types.py +0 -0
  216. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/__init__.py +0 -0
  217. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/agent_profile.py +0 -0
  218. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
  219. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/profile_refs.py +0 -0
  220. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/resolver.py +0 -0
  221. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/profiles/seed.py +0 -0
  222. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/py.typed +0 -0
  223. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/secret/__init__.py +0 -0
  224. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/secret/secrets.py +0 -0
  225. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/__init__.py +0 -0
  226. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/_shell_ast.py +0 -0
  227. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/analyzer.py +0 -0
  228. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/confirmation_policy.py +0 -0
  229. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
  230. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
  231. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
  232. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
  233. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
  234. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/ensemble.py +0 -0
  235. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
  236. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
  237. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/grayswan/utils.py +0 -0
  238. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/llm_analyzer.py +0 -0
  239. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/risk.py +0 -0
  240. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/shell_parser.py +0 -0
  241. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
  242. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
  243. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/settings/__init__.py +0 -0
  244. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/settings/acp_providers.py +0 -0
  245. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/settings/api_models.py +0 -0
  246. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/settings/metadata.py +0 -0
  247. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/__init__.py +0 -0
  248. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/exceptions.py +0 -0
  249. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/execute.py +0 -0
  250. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/fetch.py +0 -0
  251. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/installed.py +0 -0
  252. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/skill.py +0 -0
  253. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/trigger.py +0 -0
  254. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/types.py +0 -0
  255. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/skills/utils.py +0 -0
  256. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/subagent/__init__.py +0 -0
  257. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/subagent/load.py +0 -0
  258. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/subagent/registry.py +0 -0
  259. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/subagent/schema.py +0 -0
  260. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/testing/__init__.py +0 -0
  261. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/testing/test_llm.py +0 -0
  262. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/__init__.py +0 -0
  263. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/finish.py +0 -0
  264. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
  265. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
  266. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/think.py +0 -0
  267. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
  268. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/client_tool.py +0 -0
  269. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/defaults.py +0 -0
  270. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/registry.py +0 -0
  271. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/schema.py +0 -0
  272. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/spec.py +0 -0
  273. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/tool/tool.py +0 -0
  274. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/__init__.py +0 -0
  275. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/async_utils.py +0 -0
  276. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/cipher.py +0 -0
  277. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/command.py +0 -0
  278. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/datetime.py +0 -0
  279. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/deprecation.py +0 -0
  280. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/files.py +0 -0
  281. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/github.py +0 -0
  282. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/json.py +0 -0
  283. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/models.py +0 -0
  284. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/paging.py +0 -0
  285. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/path.py +0 -0
  286. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  287. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/redact.py +0 -0
  288. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/truncate.py +0 -0
  289. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/utils/visualize.py +0 -0
  290. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/__init__.py +0 -0
  291. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/base.py +0 -0
  292. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/local.py +0 -0
  293. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/models.py +0 -0
  294. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
  295. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
  296. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
  297. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands/sdk/workspace/workspace.py +0 -0
  298. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands_sdk.egg-info/SOURCES.txt +0 -0
  299. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  300. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands_sdk.egg-info/requires.txt +0 -0
  301. {openhands_sdk-1.43.0 → openhands_sdk-1.44.0}/openhands_sdk.egg-info/top_level.txt +0 -0
  302. {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.43.0
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
- if self.llm.stream:
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 = self.llm.completion(
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 self.llm.acompletion(messages=messages)
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}"
@@ -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
 
@@ -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
- if new_llm.is_subscription:
1663
- if self.agent.condenser is not None:
1664
- self._subscription_disabled_condenser = self.agent.condenser
1665
- update["condenser"] = None
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
- with open(full_path, "w", encoding="utf-8") as f:
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.is_subscription:
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 (subscription mode
1771
- # is exempt — it streams internally without an on_token). See #4014.
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.is_subscription:
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 (subscription mode
1921
- # is exempt — it streams internally without an on_token). See #4014.
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 = None if llm.is_subscription else self.build_condenser(llm)
1392
+ condenser = self.build_condenser(llm)
1393
1393
  return Agent(
1394
1394
  llm=llm,
1395
1395
  tools=tools,
@@ -1,8 +1,9 @@
1
- """Implementing essential tools that doesn't interact with the environment.
1
+ """SDK-resident tools that do not interact with the environment.
2
2
 
3
- These are built in and are *required* for the agent to work.
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
- For tools that require interacting with the environment, add them to `openhands-tools`.
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
- """Call ``GET /api/settings`` and return a validated settings model.
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. The outer response is
331
- validated via :class:`SettingsResponse`, then the ``agent_settings``
332
- dict is validated through :meth:`SettingsResponse.get_agent_settings`,
333
- which applies the persisted settings migration entry point before
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
- headers = dict(self._headers)
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 LLM settings from persisted settings or a named profile.
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 persisted LLM settings.
376
- **llm_kwargs: Additional keyword arguments that override persisted
377
- or profile values (e.g., ``model``, ``temperature``).
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 persisted settings or profile.
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(url: str, provider: GitProvider, token: str | None) -> str:
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
- Uses proper URL parsing to prevent token injection into malicious URLs.
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
- # Handle full URLs - inject authentication only if hostname matches exactly
315
- if token:
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
- return url
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(repo.url, provider, token)
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.43.0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openhands-sdk"
3
- version = "1.43.0"
3
+ version = "1.44.0"
4
4
  description = "OpenHands SDK - Core functionality for building AI agents"
5
5
 
6
6
  requires-python = ">=3.12"