openhands-sdk 1.29.0__tar.gz → 1.29.2__tar.gz

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