openhands-sdk 1.29.2__tar.gz → 1.29.3__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 (291) hide show
  1. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/PKG-INFO +1 -1
  2. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/agent/agent.py +45 -0
  3. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/agent/base.py +43 -16
  4. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/agent_context.py +19 -26
  5. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/prompts/presets.py +3 -3
  6. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/prompts/sections/dynamic.py +1 -2
  7. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/prompts/sections/static.py +10 -5
  8. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/impl/local_conversation.py +60 -1
  9. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/exceptions/__init__.py +4 -0
  10. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/exceptions/classifier.py +38 -21
  11. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/exceptions/mapping.py +7 -0
  12. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/exceptions/types.py +14 -0
  13. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/retry_mixin.py +9 -1
  14. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/plugin/__init__.py +9 -0
  15. openhands_sdk-1.29.3/openhands/sdk/plugin/discovery.py +203 -0
  16. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands_sdk.egg-info/PKG-INFO +1 -1
  17. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands_sdk.egg-info/SOURCES.txt +2 -28
  18. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/pyproject.toml +1 -1
  19. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/in_context_learning_example.j2 +0 -175
  20. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +0 -3
  21. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +0 -3
  22. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +0 -1
  23. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +0 -2
  24. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +0 -18
  25. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/security_policy.j2 +0 -25
  26. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/security_risk_assessment.j2 +0 -31
  27. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/self_documentation.j2 +0 -15
  28. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt.j2 +0 -152
  29. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt_interactive.j2 +0 -14
  30. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +0 -40
  31. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +0 -122
  32. openhands_sdk-1.29.2/openhands/sdk/context/prompts/templates/system_message_suffix.j2 +0 -52
  33. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/__init__.py +0 -0
  34. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/agent/__init__.py +0 -0
  35. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/agent/acp_agent.py +0 -0
  36. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/agent/acp_models.py +0 -0
  37. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/agent/critic_mixin.py +0 -0
  38. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/agent/parallel_executor.py +0 -0
  39. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/agent/prompts/system_prompt_planning.j2 +0 -0
  40. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/agent/response_dispatch.py +0 -0
  41. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/agent/utils.py +0 -0
  42. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/banner.py +0 -0
  43. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/__init__.py +0 -0
  44. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/condenser/__init__.py +0 -0
  45. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/condenser/base.py +0 -0
  46. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
  47. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  48. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  49. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
  50. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/condenser/utils.py +0 -0
  51. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/prompts/__init__.py +0 -0
  52. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/prompts/prompt.py +0 -0
  53. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/prompts/registry.py +0 -0
  54. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/prompts/section.py +0 -0
  55. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
  56. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  57. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  58. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/view/__init__.py +0 -0
  59. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/view/manipulation_indices.py +0 -0
  60. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/view/properties/__init__.py +0 -0
  61. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/view/properties/base.py +0 -0
  62. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
  63. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
  64. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
  65. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
  66. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/context/view/view.py +0 -0
  67. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/__init__.py +0 -0
  68. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/base.py +0 -0
  69. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/cancellation.py +0 -0
  70. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/conversation.py +0 -0
  71. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/conversation_stats.py +0 -0
  72. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/event_store.py +0 -0
  73. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/events_list_base.py +0 -0
  74. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/exceptions.py +0 -0
  75. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/fifo_lock.py +0 -0
  76. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/goal/__init__.py +0 -0
  77. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/goal/controller.py +0 -0
  78. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/goal/judge.py +0 -0
  79. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/goal/prompts.py +0 -0
  80. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/goal/runner.py +0 -0
  81. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/impl/__init__.py +0 -0
  82. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/impl/remote_conversation.py +0 -0
  83. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/persistence_const.py +0 -0
  84. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/request.py +0 -0
  85. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
  86. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/response_utils.py +0 -0
  87. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/secret_registry.py +0 -0
  88. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/state.py +0 -0
  89. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/stuck_detector.py +0 -0
  90. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/title_utils.py +0 -0
  91. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/types.py +0 -0
  92. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  93. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/visualizer/base.py +0 -0
  94. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/conversation/visualizer/default.py +0 -0
  95. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/__init__.py +0 -0
  96. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/base.py +0 -0
  97. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/impl/__init__.py +0 -0
  98. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  99. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  100. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  101. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/impl/api/client.py +0 -0
  102. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/impl/api/critic.py +0 -0
  103. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  104. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  105. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  106. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/critic/result.py +0 -0
  107. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/__init__.py +0 -0
  108. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/acp_tool_call.py +0 -0
  109. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/base.py +0 -0
  110. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/condenser.py +0 -0
  111. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/conversation_error.py +0 -0
  112. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/conversation_state.py +0 -0
  113. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/hook_execution.py +0 -0
  114. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/llm_completion_log.py +0 -0
  115. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  116. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/llm_convertible/action.py +0 -0
  117. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/llm_convertible/message.py +0 -0
  118. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  119. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
  120. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/llm_convertible/system.py +0 -0
  121. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/resume_transcript.py +0 -0
  122. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/streaming_delta.py +0 -0
  123. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/token.py +0 -0
  124. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/types.py +0 -0
  125. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/event/user_action.py +0 -0
  126. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/extensions/__init__.py +0 -0
  127. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/extensions/fetch.py +0 -0
  128. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/extensions/installation/__init__.py +0 -0
  129. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/extensions/installation/info.py +0 -0
  130. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/extensions/installation/interface.py +0 -0
  131. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/extensions/installation/manager.py +0 -0
  132. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/extensions/installation/metadata.py +0 -0
  133. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/extensions/installation/utils.py +0 -0
  134. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/git/cached_repo.py +0 -0
  135. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/git/exceptions.py +0 -0
  136. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/git/git_changes.py +0 -0
  137. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/git/git_diff.py +0 -0
  138. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/git/models.py +0 -0
  139. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/git/utils.py +0 -0
  140. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/hooks/__init__.py +0 -0
  141. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/hooks/config.py +0 -0
  142. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  143. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/hooks/executor.py +0 -0
  144. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/hooks/manager.py +0 -0
  145. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/hooks/types.py +0 -0
  146. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/io/__init__.py +0 -0
  147. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/io/base.py +0 -0
  148. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/io/cache.py +0 -0
  149. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/io/local.py +0 -0
  150. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/io/memory.py +0 -0
  151. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/__init__.py +0 -0
  152. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/auth/__init__.py +0 -0
  153. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/auth/credentials.py +0 -0
  154. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/auth/openai.py +0 -0
  155. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/fallback_strategy.py +0 -0
  156. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/llm.py +0 -0
  157. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/llm_profile_store.py +0 -0
  158. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/llm_registry.py +0 -0
  159. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/llm_response.py +0 -0
  160. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/message.py +0 -0
  161. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  162. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
  163. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  164. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/options/__init__.py +0 -0
  165. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/options/chat_options.py +0 -0
  166. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/options/common.py +0 -0
  167. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/options/responses_options.py +0 -0
  168. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/router/__init__.py +0 -0
  169. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/router/base.py +0 -0
  170. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  171. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/router/impl/random.py +0 -0
  172. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/streaming.py +0 -0
  173. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/image_inline.py +0 -0
  174. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/image_resize.py +0 -0
  175. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
  176. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/metrics.py +0 -0
  177. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/model_features.py +0 -0
  178. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/model_info.py +0 -0
  179. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  180. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
  181. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
  182. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/telemetry.py +0 -0
  183. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  184. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/verified_models.py +0 -0
  185. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
  186. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/logger/__init__.py +0 -0
  187. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/logger/logger.py +0 -0
  188. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/logger/rolling.py +0 -0
  189. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/marketplace/__init__.py +0 -0
  190. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/marketplace/registration.py +0 -0
  191. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/marketplace/registry.py +0 -0
  192. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/marketplace/types.py +0 -0
  193. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/mcp/__init__.py +0 -0
  194. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/mcp/client.py +0 -0
  195. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/mcp/definition.py +0 -0
  196. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/mcp/exceptions.py +0 -0
  197. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/mcp/tool.py +0 -0
  198. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/mcp/utils.py +0 -0
  199. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/observability/__init__.py +0 -0
  200. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/observability/laminar.py +0 -0
  201. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/observability/utils.py +0 -0
  202. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/plugin/fetch.py +0 -0
  203. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/plugin/installed.py +0 -0
  204. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/plugin/loader.py +0 -0
  205. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/plugin/plugin.py +0 -0
  206. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/plugin/source.py +0 -0
  207. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/plugin/types.py +0 -0
  208. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/profiles/__init__.py +0 -0
  209. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/profiles/agent_profile.py +0 -0
  210. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/profiles/agent_profile_store.py +0 -0
  211. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/profiles/profile_refs.py +0 -0
  212. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/profiles/resolver.py +0 -0
  213. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/py.typed +0 -0
  214. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/secret/__init__.py +0 -0
  215. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/secret/secrets.py +0 -0
  216. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/__init__.py +0 -0
  217. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/_shell_ast.py +0 -0
  218. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/analyzer.py +0 -0
  219. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/confirmation_policy.py +0 -0
  220. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
  221. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
  222. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
  223. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
  224. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/ensemble.py +0 -0
  225. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/grayswan/__init__.py +0 -0
  226. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/grayswan/analyzer.py +0 -0
  227. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/grayswan/utils.py +0 -0
  228. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/llm_analyzer.py +0 -0
  229. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/risk.py +0 -0
  230. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/security/shell_parser.py +0 -0
  231. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/settings/__init__.py +0 -0
  232. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/settings/acp_providers.py +0 -0
  233. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/settings/api_models.py +0 -0
  234. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/settings/metadata.py +0 -0
  235. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/settings/model.py +0 -0
  236. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/skills/__init__.py +0 -0
  237. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/skills/exceptions.py +0 -0
  238. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/skills/execute.py +0 -0
  239. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/skills/fetch.py +0 -0
  240. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/skills/installed.py +0 -0
  241. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/skills/skill.py +0 -0
  242. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/skills/trigger.py +0 -0
  243. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/skills/types.py +0 -0
  244. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/skills/utils.py +0 -0
  245. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/subagent/__init__.py +0 -0
  246. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/subagent/load.py +0 -0
  247. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/subagent/registry.py +0 -0
  248. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/subagent/schema.py +0 -0
  249. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/testing/__init__.py +0 -0
  250. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/testing/test_llm.py +0 -0
  251. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/tool/__init__.py +0 -0
  252. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/tool/builtins/__init__.py +0 -0
  253. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/tool/builtins/finish.py +0 -0
  254. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
  255. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
  256. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/tool/builtins/think.py +0 -0
  257. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/tool/client_tool.py +0 -0
  258. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/tool/registry.py +0 -0
  259. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/tool/schema.py +0 -0
  260. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/tool/spec.py +0 -0
  261. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/tool/tool.py +0 -0
  262. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/__init__.py +0 -0
  263. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/async_executor.py +0 -0
  264. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/async_utils.py +0 -0
  265. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/cipher.py +0 -0
  266. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/command.py +0 -0
  267. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/datetime.py +0 -0
  268. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/deprecation.py +0 -0
  269. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/github.py +0 -0
  270. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/json.py +0 -0
  271. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/models.py +0 -0
  272. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/paging.py +0 -0
  273. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/path.py +0 -0
  274. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  275. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/redact.py +0 -0
  276. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/truncate.py +0 -0
  277. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/utils/visualize.py +0 -0
  278. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/workspace/__init__.py +0 -0
  279. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/workspace/base.py +0 -0
  280. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/workspace/local.py +0 -0
  281. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/workspace/models.py +0 -0
  282. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/workspace/remote/__init__.py +0 -0
  283. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
  284. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/workspace/remote/base.py +0 -0
  285. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
  286. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/workspace/repo.py +0 -0
  287. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands/sdk/workspace/workspace.py +0 -0
  288. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  289. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands_sdk.egg-info/requires.txt +0 -0
  290. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/openhands_sdk.egg-info/top_level.txt +0 -0
  291. {openhands_sdk-1.29.2 → openhands_sdk-1.29.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.29.2
3
+ Version: 1.29.3
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
@@ -61,6 +61,7 @@ from openhands.sdk.llm import (
61
61
  )
62
62
  from openhands.sdk.llm.exceptions import (
63
63
  FunctionCallValidationError,
64
+ LLMContentPolicyViolationError,
64
65
  LLMContextWindowExceedError,
65
66
  LLMMalformedConversationHistoryError,
66
67
  )
@@ -607,6 +608,28 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
607
608
  )
608
609
  on_event(error_message)
609
610
  return
611
+ except LLMContentPolicyViolationError as e:
612
+ # Content-policy blocks are deterministic; nudge the model and let the
613
+ # run loop continue instead of emitting a fatal error.
614
+ logger.warning(f"LLM output blocked by content filter: {e}")
615
+ on_event(
616
+ MessageEvent(
617
+ source="user",
618
+ llm_message=Message(
619
+ role="user",
620
+ content=[
621
+ TextContent(
622
+ text=(
623
+ "Your previous response was blocked by the "
624
+ "model's content filter. Please continue, "
625
+ "rephrasing to avoid the flagged content."
626
+ )
627
+ )
628
+ ],
629
+ ),
630
+ )
631
+ )
632
+ return
610
633
  except LLMMalformedConversationHistoryError as e:
611
634
  # The provider rejected the current message history as structurally
612
635
  # invalid (for example, broken tool_use/tool_result pairing). Route
@@ -747,6 +770,28 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
747
770
  )
748
771
  on_event(error_message)
749
772
  return
773
+ except LLMContentPolicyViolationError as e:
774
+ # Content-policy blocks are deterministic; nudge the model and let the
775
+ # run loop continue instead of emitting a fatal error.
776
+ logger.warning(f"LLM output blocked by content filter: {e}")
777
+ on_event(
778
+ MessageEvent(
779
+ source="user",
780
+ llm_message=Message(
781
+ role="user",
782
+ content=[
783
+ TextContent(
784
+ text=(
785
+ "Your previous response was blocked by the "
786
+ "model's content filter. Please continue, "
787
+ "rephrasing to avoid the flagged content."
788
+ )
789
+ )
790
+ ],
791
+ ),
792
+ )
793
+ )
794
+ return
750
795
  except LLMMalformedConversationHistoryError as e:
751
796
  # The provider rejected the current message history as
752
797
  # structurally invalid (for example, broken
@@ -9,6 +9,7 @@ from abc import ABC, abstractmethod
9
9
  from collections import Counter
10
10
  from collections.abc import Generator, Iterable, Sequence
11
11
  from concurrent.futures import ThreadPoolExecutor
12
+ from pathlib import Path
12
13
  from typing import TYPE_CHECKING, Any, Literal
13
14
 
14
15
  from pydantic import (
@@ -248,10 +249,14 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
248
249
  security_policy_filename: str = Field(
249
250
  default="security_policy.j2",
250
251
  description=(
251
- "Security policy template filename. Can be either:\n"
252
- "- A relative filename (e.g., 'security_policy.j2') loaded from the "
253
- "agent's prompts directory\n"
254
- "- An absolute path (e.g., '/path/to/custom_security_policy.j2')\n"
252
+ "Security policy filename. The default 'security_policy.j2' is a "
253
+ "back-compat sentinel (the file was removed) that selects the built-in "
254
+ "default policy from the prompt registry -- it is not loaded from disk. "
255
+ "Any other value names a custom policy file whose contents are inserted "
256
+ "verbatim (NOT rendered as a Jinja template). Can be either:\n"
257
+ "- A relative filename (e.g., 'custom_security_policy.md') loaded from "
258
+ "the agent's prompts directory\n"
259
+ "- An absolute path (e.g., '/path/to/custom_security_policy.md')\n"
255
260
  "- Empty string to disable security policy"
256
261
  ),
257
262
  )
@@ -464,12 +469,10 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
464
469
  per-conversation context. This static portion can be cached and reused
465
470
  across conversations for better prompt caching efficiency.
466
471
 
467
- The default prompt is assembled from the typed section registry
468
- (``create_registry``). Escape hatches keep the Jinja render path: an inline
469
- ``system_prompt`` is returned verbatim; a custom/absolute
470
- ``system_prompt_filename`` renders through ``render_template``; a subclass
471
- with its own ``prompt_dir`` still renders its default-named template; and a
472
- custom ``security_policy_filename`` renders so its policy file is included.
472
+ The default prompt is assembled from the typed section registry, which also
473
+ resolves a custom ``security_policy_filename``. Escape hatches keep the Jinja
474
+ path: an inline ``system_prompt`` is returned verbatim; a custom
475
+ ``system_prompt_filename`` or subclass ``prompt_dir`` renders its own template.
473
476
 
474
477
  Returns:
475
478
  The static system prompt without dynamic context.
@@ -477,14 +480,11 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
477
480
  if self.system_prompt is not None:
478
481
  return self.system_prompt
479
482
 
480
- # Escape hatch: custom/absolute filename, a subclass with its own
481
- # prompt_dir, or a custom security policy. The registry reproduces only
482
- # the built-in default prompt (default template + default policy); a
483
- # non-default security_policy_filename must keep the Jinja include path.
483
+ # Escape hatch: a custom filename or a subclass's own prompt_dir renders its
484
+ # own Jinja template; everything else (incl. custom policies) uses the registry.
484
485
  if (
485
486
  self.system_prompt_filename != "system_prompt.j2"
486
487
  or os.path.realpath(self.prompt_dir) != _BUILTIN_PROMPT_DIR
487
- or self.security_policy_filename != "security_policy.j2"
488
488
  ):
489
489
  return render_template(
490
490
  prompt_dir=self.prompt_dir,
@@ -525,6 +525,24 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
525
525
  template_kwargs["model_variant"] = spec.variant
526
526
  return template_kwargs
527
527
 
528
+ def _read_custom_security_policy(self) -> str | None:
529
+ """Raw contents of a custom security policy file -- inserted verbatim, NOT
530
+ rendered as a Jinja template.
531
+
532
+ Returns ``None`` -- so ``SecuritySection`` keeps its built-in default policy
533
+ -- when ``security_policy_filename`` is the default sentinel
534
+ ``"security_policy.j2"`` (a string only; the file was removed, so it is never
535
+ read) or ``""`` (an empty *filename*, which disables the policy). A configured
536
+ file whose own contents are empty still returns ``""`` (an empty custom
537
+ policy), not ``None``.
538
+
539
+ Relative names resolve against ``prompt_dir``; absolute paths are used as-is.
540
+ """
541
+ filename = self.security_policy_filename
542
+ if not filename or filename == "security_policy.j2":
543
+ return None
544
+ return (Path(self.prompt_dir) / filename).read_text(encoding="utf-8")
545
+
528
546
  def _build_prompt_context(
529
547
  self,
530
548
  additional_secret_infos: list[dict[str, str | None]] | None = None,
@@ -578,8 +596,17 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
578
596
  # secrets (additional_secret_infos), matching what <CUSTOM_SECRETS> shows.
579
597
  secret_names = tuple(name for name, _ in secret_infos if name)
580
598
 
599
+ template_kwargs = self._resolved_template_kwargs()
600
+ # A custom security policy's content for SecuritySection (registry path only).
601
+ policy_content = self._read_custom_security_policy()
602
+ if policy_content is not None:
603
+ template_kwargs = {
604
+ **template_kwargs,
605
+ "security_policy_content": policy_content,
606
+ }
607
+
581
608
  return PromptContext(
582
- template_kwargs=self._resolved_template_kwargs(),
609
+ template_kwargs=template_kwargs,
583
610
  tool_names=tuple(t.name for t in self.tools),
584
611
  platform=Platform.current(),
585
612
  working_dir=None,
@@ -16,6 +16,8 @@ from pydantic import (
16
16
  )
17
17
 
18
18
  from openhands.sdk.context.prompts import render_template
19
+ from openhands.sdk.context.prompts.presets import create_registry
20
+ from openhands.sdk.context.prompts.section import PromptContext
19
21
  from openhands.sdk.llm import Message, TextContent
20
22
  from openhands.sdk.llm.utils.model_prompt_spec import get_model_prompt_spec
21
23
  from openhands.sdk.logger import get_logger
@@ -41,8 +43,8 @@ PROMPT_DIR = pathlib.Path(__file__).parent / "prompts" / "templates"
41
43
 
42
44
 
43
45
  class ResolvedDynamicData(NamedTuple):
44
- """Dynamic-tier inputs resolved once, shared by the legacy ``.j2`` renderer and
45
- the section registry (skills gated by model family, secrets merged)."""
46
+ """Dynamic-tier inputs resolved once, fed into the section registry
47
+ (skills gated by model family, secrets merged)."""
46
48
 
47
49
  repo_skills: list[Skill]
48
50
  available_skills_prompt: str
@@ -327,27 +329,16 @@ class AgentContext(BaseModel):
327
329
  data = self._resolve_dynamic_data(
328
330
  llm_model, llm_model_canonical, additional_secret_infos
329
331
  )
330
- has_content = (
331
- data.repo_skills
332
- or self.system_message_suffix
333
- or data.secret_infos
334
- or data.available_skills_prompt
335
- or data.formatted_datetime
332
+ ctx = PromptContext(
333
+ now=data.formatted_datetime,
334
+ repo_skills=tuple((s.name, s.content) for s in data.repo_skills),
335
+ available_skills_prompt=data.available_skills_prompt or None,
336
+ custom_suffix=self.system_message_suffix or None,
337
+ secret_infos=tuple(
338
+ (info["name"] or "", info["description"]) for info in data.secret_infos
339
+ ),
336
340
  )
337
- if has_content:
338
- formatted_text = render_template(
339
- prompt_dir=str(PROMPT_DIR),
340
- template_name="system_message_suffix.j2",
341
- repo_skills=data.repo_skills,
342
- system_message_suffix=self.system_message_suffix or "",
343
- secret_infos=data.secret_infos,
344
- available_skills_prompt=data.available_skills_prompt,
345
- current_datetime=data.formatted_datetime,
346
- ).strip()
347
- return formatted_text
348
- elif self.system_message_suffix and self.system_message_suffix.strip():
349
- return self.system_message_suffix.strip()
350
- return None
341
+ return create_registry().build(ctx).dynamic
351
342
 
352
343
  def _resolve_dynamic_data(
353
344
  self,
@@ -433,9 +424,10 @@ class AgentContext(BaseModel):
433
424
  this adapter only emits prompt-only context. Unsupported AgentContext
434
425
  fields are rejected by :meth:`validate_acp_compatibility`.
435
426
 
436
- The rendering reuses :meth:`get_system_message_suffix` with the same
437
- ``system_message_suffix.j2`` template so that ACP agents receive the
438
- identical prompt layout as the regular agent. This includes the
427
+ The rendering reuses :meth:`get_system_message_suffix`, which assembles
428
+ the dynamic-tier sections via the shared prompt registry, so that ACP
429
+ agents receive the identical prompt layout as the regular agent. This
430
+ includes the
439
431
  ``<CUSTOM_SECRETS>`` block when secrets are present, informing the ACP
440
432
  subprocess which environment variables are available. The actual secret
441
433
  values are injected into the subprocess environment by
@@ -455,7 +447,8 @@ class AgentContext(BaseModel):
455
447
  """
456
448
  self.validate_acp_compatibility()
457
449
  # No model-specific skill filtering for ACP — delegate to the shared
458
- # renderer which also renders the <CUSTOM_SECRETS> block from secrets.
450
+ # builder, whose dynamic-tier sections also emit the <CUSTOM_SECRETS>
451
+ # block from secrets.
459
452
  return self.get_system_message_suffix(
460
453
  additional_secret_infos=additional_secret_infos
461
454
  )
@@ -2,8 +2,8 @@
2
2
 
3
3
  ``create_registry()`` registers the static-tier sections in the exact order
4
4
  ``agent/prompts/system_prompt.j2`` emits them, so ``registry.build(ctx).static``
5
- reproduces ``AgentBase.static_system_message``. The dynamic-tier sections
6
- (``system_message_suffix.j2``) are appended separately. It will gain a ``preset``
5
+ reproduces ``AgentBase.static_system_message``. The dynamic-tier sections are
6
+ appended separately. It will gain a ``preset``
7
7
  flag to select among prompt variants (interactive, planning, ...) over the same
8
8
  engine; today it returns the default composition.
9
9
  """
@@ -62,7 +62,7 @@ def create_registry() -> PromptRegistry:
62
62
  r.register(TroubleshootingSection())
63
63
  r.register(ProcessManagementSection())
64
64
  r.register(ModelSpecificSection()) # guard: model_family resolved
65
- # dynamic tier -- ported verbatim from system_message_suffix.j2 (#3610)
65
+ # dynamic tier (#3610)
66
66
  r.register(DateTimeSection())
67
67
  r.register(RepoContextSection()) # guard: gated repo skills present
68
68
  r.register(AvailableSkillsSection()) # guard: available_skills_prompt
@@ -1,5 +1,4 @@
1
- """Dynamic-tier prompt sections ported verbatim from
2
- ``context/prompts/templates/system_message_suffix.j2``.
1
+ """Dynamic-tier prompt sections.
3
2
 
4
3
  These render per-conversation content (datetime, repo context, available skills,
5
4
  custom suffix, secrets) into the ``DYNAMIC`` block. All inputs are resolved into the
@@ -219,11 +219,9 @@ Always provide links to the relevant documentation pages for users who want to l
219
219
 
220
220
 
221
221
  class SecuritySection(_StaticTextSection):
222
- """The ``<SECURITY>`` block wrapping the default security policy.
223
-
224
- Guarded by ``security_policy_filename`` (empty string disables it). The body is
225
- the default policy; a custom ``security_policy_filename`` would resolve its content
226
- into the context instead (a follow-up; not exercised by the snapshot matrix).
222
+ """The ``<SECURITY>`` block: the built-in default policy, or a custom policy's
223
+ ``security_policy_content`` when configured. Guarded by ``security_policy_filename``
224
+ (empty string disables it).
227
225
  """
228
226
 
229
227
  name = "security"
@@ -261,6 +259,13 @@ class SecuritySection(_StaticTextSection):
261
259
  def guard(self, ctx: PromptContext) -> bool:
262
260
  return bool(ctx.template_kwargs.get("security_policy_filename"))
263
261
 
262
+ def render(self, ctx: PromptContext) -> str | None:
263
+ content = ctx.template_kwargs.get("security_policy_content")
264
+ # `is not None`: an explicitly empty custom policy must not fall back to body.
265
+ if content is not None:
266
+ return _refine(f"<SECURITY>\n\n{content}\n\n</SECURITY>", ctx.platform)
267
+ return self.body
268
+
264
269
 
265
270
  class SecurityRiskAssessmentSection:
266
271
  """``<SECURITY_RISK_ASSESSMENT>`` -- the LOW/MEDIUM/HIGH tiers swap with ``cli_mode``."""
@@ -61,6 +61,7 @@ from openhands.sdk.plugin import (
61
61
  PluginSource,
62
62
  ResolvedPluginSource,
63
63
  fetch_plugin_with_resolution,
64
+ load_available_plugins,
64
65
  )
65
66
  from openhands.sdk.secret import StaticSecret
66
67
  from openhands.sdk.security.analyzer import SecurityAnalyzerBase
@@ -662,6 +663,10 @@ class LocalConversation(BaseConversation):
662
663
 
663
664
  all_plugin_hooks: list[HookConfig] = []
664
665
  all_plugin_agents: list[AgentDefinition] = []
666
+ # Names of explicitly-attached plugins (populated in the loop below). Used
667
+ # to keep explicit attach authoritative over ambient installed/local
668
+ # plugins (and to avoid double-registering their hooks/agents).
669
+ explicit_plugin_names: set[str] = set()
665
670
 
666
671
  merged_context = self.agent.agent_context
667
672
  merged_mcp = dict(self.agent.mcp_config) if self.agent.mcp_config else {}
@@ -743,6 +748,7 @@ class LocalConversation(BaseConversation):
743
748
  f"Loaded plugin '{plugin.manifest.name}'"
744
749
  + (f" @ {resolved_ref[:8]}" if resolved_ref else "")
745
750
  )
751
+ explicit_plugin_names.add(plugin.name)
746
752
 
747
753
  # Merge plugin contents
748
754
  merged_context = plugin.add_skills_to(merged_context)
@@ -759,6 +765,54 @@ class LocalConversation(BaseConversation):
759
765
 
760
766
  logger.info(f"Loaded {len(plugins_to_load)} plugin(s) via Conversation")
761
767
 
768
+ # Ambient plugins: enabled installed plugins plus local user/project
769
+ # plugins, mirroring how installed/local skills already auto-load. These
770
+ # are additive to the explicit plugins above, de-duplicated by plugin
771
+ # name. Explicit attach wins: an ambient plugin whose name was already
772
+ # attached above is skipped (this also avoids double-registering its
773
+ # hooks/agents). Best-effort — a failure here must not prevent the
774
+ # conversation from starting.
775
+ #
776
+ # Ambient plugins have no pinned commit SHA, so (unlike explicit attach)
777
+ # they are intentionally NOT recorded in self._resolved_plugins. On
778
+ # resume they are re-discovered from disk / current enabled state, just
779
+ # like project skills. Automation/sandbox runs lack the user's installed
780
+ # and home directories, so discovery naturally yields nothing there.
781
+ ambient_plugins_loaded = False
782
+ try:
783
+ ambient_plugins = load_available_plugins(
784
+ work_dir=self.workspace.working_dir,
785
+ include_user=True,
786
+ include_project=True,
787
+ )
788
+ except Exception:
789
+ logger.warning(
790
+ "Failed to load ambient (installed/local) plugins; "
791
+ "continuing without them",
792
+ exc_info=True,
793
+ )
794
+ ambient_plugins = {}
795
+
796
+ for plugin in ambient_plugins.values():
797
+ if plugin.name in explicit_plugin_names:
798
+ logger.debug(
799
+ f"Skipping ambient plugin '{plugin.name}' "
800
+ "(explicitly attached to this conversation)"
801
+ )
802
+ continue
803
+
804
+ merged_context = plugin.add_skills_to(merged_context)
805
+ merged_mcp = plugin.add_mcp_config_to(merged_mcp)
806
+ has_mcp_config = has_mcp_config or bool(merged_mcp)
807
+
808
+ if plugin.hooks and not plugin.hooks.is_empty():
809
+ all_plugin_hooks.append(plugin.hooks)
810
+ if plugin.agents:
811
+ all_plugin_agents.extend(plugin.agents)
812
+
813
+ ambient_plugins_loaded = True
814
+ logger.debug(f"Loaded ambient plugin '{plugin.name}'")
815
+
762
816
  # Resolve project skills from the workspace. AgentContext can't do this
763
817
  # itself (the workspace path is unknown at validation time), so it is done
764
818
  # here, where the path is known. Project skills take precedence over
@@ -810,7 +864,12 @@ class LocalConversation(BaseConversation):
810
864
 
811
865
  # Update agent with merged content only if something changed.
812
866
  # Skip update otherwise to avoid unnecessary agent state mutations.
813
- if plugins_to_load or has_mcp_config or project_skills_loaded:
867
+ if (
868
+ plugins_to_load
869
+ or has_mcp_config
870
+ or project_skills_loaded
871
+ or ambient_plugins_loaded
872
+ ):
814
873
  self.agent = self.agent.model_copy(
815
874
  update={
816
875
  "agent_context": merged_context,
@@ -1,4 +1,5 @@
1
1
  from .classifier import (
2
+ is_content_policy_violation,
2
3
  is_context_window_exceeded,
3
4
  is_prompt_cache_too_small,
4
5
  looks_like_auth_error,
@@ -11,6 +12,7 @@ from .types import (
11
12
  FunctionCallValidationError,
12
13
  LLMAuthenticationError,
13
14
  LLMBadRequestError,
15
+ LLMContentPolicyViolationError,
14
16
  LLMContextWindowExceedError,
15
17
  LLMContextWindowTooSmallError,
16
18
  LLMError,
@@ -40,6 +42,7 @@ __all__ = [
40
42
  "LLMContextWindowExceedError",
41
43
  "LLMMalformedConversationHistoryError",
42
44
  "LLMContextWindowTooSmallError",
45
+ "LLMContentPolicyViolationError",
43
46
  "LLMAuthenticationError",
44
47
  "LLMRateLimitError",
45
48
  "LLMTimeoutError",
@@ -48,6 +51,7 @@ __all__ = [
48
51
  "UserCancelledError",
49
52
  "OperationCancelled",
50
53
  # Helpers
54
+ "is_content_policy_violation",
51
55
  "is_context_window_exceeded",
52
56
  "is_prompt_cache_too_small",
53
57
  "looks_like_auth_error",
@@ -1,9 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import Final
4
+
3
5
  from litellm.exceptions import (
4
6
  APIConnectionError,
5
7
  AuthenticationError,
6
8
  BadRequestError,
9
+ ContentPolicyViolationError,
7
10
  ContextWindowExceededError,
8
11
  InternalServerError,
9
12
  OpenAIError,
@@ -17,7 +20,7 @@ from .types import (
17
20
 
18
21
 
19
22
  # Minimal, provider-agnostic context-window detection
20
- LONG_PROMPT_PATTERNS: list[str] = [
23
+ LONG_PROMPT_PATTERNS: Final[list[str]] = [
21
24
  "contextwindowexceedederror",
22
25
  "prompt is too long",
23
26
  "input length and `max_tokens` exceed context limit",
@@ -32,7 +35,7 @@ LONG_PROMPT_PATTERNS: list[str] = [
32
35
  # provider. They are tracked separately from true context-window errors so the
33
36
  # logs and agent control flow can preserve that distinction while still routing
34
37
  # into condensation-based recovery.
35
- MALFORMED_HISTORY_PATTERNS: list[str] = [
38
+ MALFORMED_HISTORY_PATTERNS: Final[list[str]] = [
36
39
  "tool_use ids were found without `tool_result` blocks immediately after",
37
40
  # Anthropic backtick variant
38
41
  "`tool_use` ids were found without `tool_result` blocks immediately after",
@@ -54,6 +57,30 @@ MALFORMED_HISTORY_PATTERNS: list[str] = [
54
57
  "failed to parse tool call arguments as json",
55
58
  ]
56
59
 
60
+ # Vertex AI (Gemini) rejects context-caching requests when the cached content
61
+ # is below the provider's minimum token threshold (currently 4096 tokens).
62
+ # Example error: "The cached content is of 1171 tokens. The minimum token
63
+ # count to start caching is 4096." — the `.lower()` comparison handles case
64
+ # variation across providers but won't match reworded messages; update this
65
+ # pattern if the API phrasing changes.
66
+ PROMPT_CACHE_TOO_SMALL_PATTERNS: Final[list[str]] = [
67
+ "minimum token count to start caching",
68
+ ]
69
+
70
+ AUTH_PATTERNS: Final[list[str]] = [
71
+ "invalid api key",
72
+ "unauthorized",
73
+ "missing api key",
74
+ "invalid authentication",
75
+ "access denied",
76
+ ]
77
+
78
+ CONTENT_POLICY_PATTERNS: Final[list[str]] = [
79
+ "content_policy",
80
+ "content filtering policy",
81
+ "output blocked by content filtering",
82
+ ]
83
+
57
84
 
58
85
  def is_context_window_exceeded(exception: Exception) -> bool:
59
86
  if isinstance(exception, (ContextWindowExceededError, LLMContextWindowExceedError)):
@@ -86,25 +113,6 @@ def looks_like_malformed_conversation_history_error(exception: Exception) -> boo
86
113
  return any(p in s for p in MALFORMED_HISTORY_PATTERNS)
87
114
 
88
115
 
89
- # Vertex AI (Gemini) rejects context-caching requests when the cached content
90
- # is below the provider's minimum token threshold (currently 4096 tokens).
91
- # Example error: "The cached content is of 1171 tokens. The minimum token
92
- # count to start caching is 4096." — the `.lower()` comparison handles case
93
- # variation across providers but won't match reworded messages; update this
94
- # pattern if the API phrasing changes.
95
- PROMPT_CACHE_TOO_SMALL_PATTERNS: list[str] = [
96
- "minimum token count to start caching",
97
- ]
98
-
99
- AUTH_PATTERNS: list[str] = [
100
- "invalid api key",
101
- "unauthorized",
102
- "missing api key",
103
- "invalid authentication",
104
- "access denied",
105
- ]
106
-
107
-
108
116
  def is_prompt_cache_too_small(exception: Exception) -> bool:
109
117
  """Return True if the error indicates the prompt cache content is too small.
110
118
 
@@ -134,3 +142,12 @@ def looks_like_auth_error(exception: Exception) -> bool:
134
142
  if code in s:
135
143
  return True
136
144
  return False
145
+
146
+
147
+ def is_content_policy_violation(exception: Exception) -> bool:
148
+ if isinstance(exception, ContentPolicyViolationError):
149
+ return True
150
+ if not isinstance(exception, (BadRequestError, OpenAIError)):
151
+ return False
152
+ s = str(exception).lower()
153
+ return any(p in s for p in CONTENT_POLICY_PATTERNS)
@@ -10,6 +10,7 @@ from litellm.exceptions import (
10
10
  )
11
11
 
12
12
  from .classifier import (
13
+ is_content_policy_violation,
13
14
  is_context_window_exceeded,
14
15
  looks_like_auth_error,
15
16
  looks_like_malformed_conversation_history_error,
@@ -17,6 +18,7 @@ from .classifier import (
17
18
  from .types import (
18
19
  LLMAuthenticationError,
19
20
  LLMBadRequestError,
21
+ LLMContentPolicyViolationError,
20
22
  LLMContextWindowExceedError,
21
23
  LLMMalformedConversationHistoryError,
22
24
  LLMRateLimitError,
@@ -56,6 +58,11 @@ def map_provider_exception(exception: Exception) -> Exception:
56
58
  ):
57
59
  return LLMServiceUnavailableError(str(exception))
58
60
 
61
+ # Content-policy blocks are deterministic 4xx; distinguish them from generic
62
+ # bad requests so the agent can recover softly instead of hard-erroring.
63
+ if is_content_policy_violation(exception):
64
+ return LLMContentPolicyViolationError(str(exception))
65
+
59
66
  # Generic client-side 4xx errors
60
67
  if isinstance(exception, BadRequestError):
61
68
  return LLMBadRequestError(str(exception))
@@ -131,6 +131,20 @@ class LLMBadRequestError(LLMError):
131
131
  super().__init__(message)
132
132
 
133
133
 
134
+ class LLMContentPolicyViolationError(LLMBadRequestError):
135
+ """Provider blocked the request/response via its content filter.
136
+
137
+ Subclasses LLMBadRequestError for back-compat. Deterministic in
138
+ (messages, model): a bare retry trips the same filter, so recovery
139
+ requires changing the request, not re-sending it.
140
+ """
141
+
142
+ def __init__(
143
+ self, message: str = "Output blocked by content filtering policy"
144
+ ) -> None:
145
+ super().__init__(message)
146
+
147
+
134
148
  # Other
135
149
  class UserCancelledError(Exception):
136
150
  def __init__(self, message: str = "User cancelled the request") -> None:
@@ -50,7 +50,15 @@ class RetryMixin:
50
50
  if isinstance(exc, LLMNoResponseError):
51
51
  kwargs = getattr(retry_state, "kwargs", None)
52
52
  if isinstance(kwargs, dict):
53
- current_temp = kwargs.get("temperature", 0)
53
+ current_temp = kwargs.get(
54
+ "temperature", getattr(self, "temperature", None)
55
+ )
56
+ if current_temp is None:
57
+ logger.warning(
58
+ "LLMNoResponseError with no configured temperature, "
59
+ "leaving temperature unset for next attempt."
60
+ )
61
+ return
54
62
  if current_temp == 0:
55
63
  kwargs["temperature"] = 1.0
56
64
  logger.warning(
@@ -12,6 +12,11 @@ user's home directory (~/.openhands/plugins/installed/).
12
12
  Note: Marketplace classes live in ``openhands.sdk.marketplace``.
13
13
  """
14
14
 
15
+ from openhands.sdk.plugin.discovery import (
16
+ load_available_plugins,
17
+ load_project_plugins,
18
+ load_user_plugins,
19
+ )
15
20
  from openhands.sdk.plugin.fetch import (
16
21
  PluginFetchError,
17
22
  fetch_plugin_with_resolution,
@@ -58,6 +63,10 @@ __all__ = [
58
63
  # Plugin loading
59
64
  "load_plugins",
60
65
  "fetch_plugin_with_resolution",
66
+ # Local plugin discovery (ambient auto-load)
67
+ "load_user_plugins",
68
+ "load_project_plugins",
69
+ "load_available_plugins",
61
70
  # Source path utilities
62
71
  "GitHubURLComponents",
63
72
  "parse_github_url",