openhands-sdk 1.55.0__tar.gz → 1.55.1__tar.gz

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