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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/PKG-INFO +1 -1
  2. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/__init__.py +2 -0
  3. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/agent.py +119 -2
  4. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/base.py +75 -25
  5. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/utils.py +8 -0
  6. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/agent_context.py +29 -31
  7. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +10 -0
  8. openhands_sdk-1.30.0/openhands/sdk/context/prompts/presets.py +109 -0
  9. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/sections/__init__.py +4 -1
  10. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/sections/dynamic.py +1 -2
  11. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt_planning.j2 → openhands_sdk-1.30.0/openhands/sdk/context/prompts/sections/planning.py +40 -2
  12. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/sections/static.py +10 -5
  13. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/base.py +20 -1
  14. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/conversation.py +12 -1
  15. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/impl/local_conversation.py +108 -28
  16. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/impl/remote_conversation.py +13 -0
  17. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/persistence_const.py +2 -1
  18. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/request.py +9 -0
  19. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/title_utils.py +5 -1
  20. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/types.py +29 -0
  21. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/__init__.py +7 -1
  22. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/auth/openai.py +17 -2
  23. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/exceptions/__init__.py +4 -0
  24. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/exceptions/classifier.py +38 -21
  25. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/exceptions/mapping.py +7 -0
  26. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/exceptions/types.py +14 -0
  27. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/llm.py +91 -9
  28. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/llm_profile_store.py +28 -1
  29. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/options/chat_options.py +21 -4
  30. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/options/responses_options.py +18 -3
  31. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/router/base.py +9 -0
  32. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/retry_mixin.py +9 -1
  33. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/marketplace/registration.py +11 -2
  34. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/tool.py +30 -4
  35. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/observability/laminar.py +23 -0
  36. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/__init__.py +9 -0
  37. openhands_sdk-1.30.0/openhands/sdk/plugin/discovery.py +203 -0
  38. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/types.py +10 -1
  39. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/profiles/__init__.py +14 -0
  40. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/profiles/agent_profile.py +35 -0
  41. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/profiles/agent_profile_store.py +131 -2
  42. openhands_sdk-1.30.0/openhands/sdk/profiles/profile_refs.py +149 -0
  43. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/profiles/resolver.py +3 -3
  44. openhands_sdk-1.30.0/openhands/sdk/profiles/seed.py +71 -0
  45. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/settings/model.py +8 -0
  46. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/subagent/__init__.py +4 -1
  47. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/subagent/load.py +45 -0
  48. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/subagent/schema.py +10 -1
  49. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/testing/test_llm.py +4 -0
  50. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/redact.py +14 -10
  51. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands_sdk.egg-info/PKG-INFO +1 -1
  52. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands_sdk.egg-info/SOURCES.txt +6 -30
  53. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/pyproject.toml +1 -1
  54. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/in_context_learning_example.j2 +0 -175
  55. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +0 -3
  56. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +0 -3
  57. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +0 -1
  58. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +0 -2
  59. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +0 -18
  60. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/security_policy.j2 +0 -25
  61. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/security_risk_assessment.j2 +0 -31
  62. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/self_documentation.j2 +0 -15
  63. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt.j2 +0 -152
  64. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt_interactive.j2 +0 -14
  65. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +0 -40
  66. openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +0 -122
  67. openhands_sdk-1.29.2/openhands/sdk/context/prompts/presets.py +0 -71
  68. openhands_sdk-1.29.2/openhands/sdk/context/prompts/templates/system_message_suffix.j2 +0 -52
  69. openhands_sdk-1.29.2/openhands/sdk/profiles/profile_refs.py +0 -173
  70. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/__init__.py +0 -0
  71. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/acp_agent.py +0 -0
  72. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/acp_models.py +0 -0
  73. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/critic_mixin.py +0 -0
  74. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/parallel_executor.py +0 -0
  75. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/response_dispatch.py +0 -0
  76. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/banner.py +0 -0
  77. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/__init__.py +0 -0
  78. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/__init__.py +0 -0
  79. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/base.py +0 -0
  80. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  81. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  82. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
  83. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/utils.py +0 -0
  84. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/__init__.py +0 -0
  85. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/prompt.py +0 -0
  86. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/registry.py +0 -0
  87. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/section.py +0 -0
  88. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  89. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  90. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/__init__.py +0 -0
  91. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
  92. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
  93. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/base.py +0 -0
  94. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
  95. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
  96. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
  97. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
  98. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/view.py +0 -0
  99. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/__init__.py +0 -0
  100. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/cancellation.py +0 -0
  101. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
  102. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/event_store.py +0 -0
  103. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/events_list_base.py +0 -0
  104. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/exceptions.py +0 -0
  105. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
  106. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
  107. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/goal/controller.py +0 -0
  108. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/goal/judge.py +0 -0
  109. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
  110. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/goal/runner.py +0 -0
  111. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
  112. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
  113. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/response_utils.py +0 -0
  114. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/secret_registry.py +0 -0
  115. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/state.py +0 -0
  116. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
  117. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  118. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
  119. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
  120. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/__init__.py +0 -0
  121. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/base.py +0 -0
  122. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/__init__.py +0 -0
  123. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  124. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  125. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  126. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/api/client.py +0 -0
  127. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
  128. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  129. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  130. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  131. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/result.py +0 -0
  132. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/__init__.py +0 -0
  133. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/acp_tool_call.py +0 -0
  134. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/base.py +0 -0
  135. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/condenser.py +0 -0
  136. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/conversation_error.py +0 -0
  137. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/conversation_state.py +0 -0
  138. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/hook_execution.py +0 -0
  139. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_completion_log.py +0 -0
  140. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  141. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
  142. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
  143. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  144. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
  145. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
  146. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/resume_transcript.py +0 -0
  147. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/streaming_delta.py +0 -0
  148. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/token.py +0 -0
  149. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/types.py +0 -0
  150. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/user_action.py +0 -0
  151. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/__init__.py +0 -0
  152. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/fetch.py +0 -0
  153. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
  154. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/info.py +0 -0
  155. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/interface.py +0 -0
  156. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/manager.py +0 -0
  157. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
  158. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/utils.py +0 -0
  159. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/cached_repo.py +0 -0
  160. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/exceptions.py +0 -0
  161. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/git_changes.py +0 -0
  162. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/git_diff.py +0 -0
  163. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/models.py +0 -0
  164. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/utils.py +0 -0
  165. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/__init__.py +0 -0
  166. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/config.py +0 -0
  167. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  168. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/executor.py +0 -0
  169. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/manager.py +0 -0
  170. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/types.py +0 -0
  171. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/io/__init__.py +0 -0
  172. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/io/base.py +0 -0
  173. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/io/cache.py +0 -0
  174. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/io/local.py +0 -0
  175. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/io/memory.py +0 -0
  176. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/auth/__init__.py +0 -0
  177. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/auth/credentials.py +0 -0
  178. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
  179. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/llm_registry.py +0 -0
  180. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/llm_response.py +0 -0
  181. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/message.py +0 -0
  182. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  183. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
  184. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  185. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/options/__init__.py +0 -0
  186. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/options/common.py +0 -0
  187. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/router/__init__.py +0 -0
  188. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  189. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/router/impl/random.py +0 -0
  190. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/streaming.py +0 -0
  191. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
  192. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
  193. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
  194. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/metrics.py +0 -0
  195. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/model_features.py +0 -0
  196. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/model_info.py +0 -0
  197. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  198. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
  199. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
  200. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
  201. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  202. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
  203. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
  204. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/logger/__init__.py +0 -0
  205. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/logger/logger.py +0 -0
  206. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/logger/rolling.py +0 -0
  207. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/marketplace/__init__.py +0 -0
  208. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/marketplace/registry.py +0 -0
  209. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/marketplace/types.py +0 -0
  210. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/__init__.py +0 -0
  211. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/client.py +0 -0
  212. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/definition.py +0 -0
  213. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/exceptions.py +0 -0
  214. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/utils.py +0 -0
  215. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/observability/__init__.py +0 -0
  216. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/observability/utils.py +0 -0
  217. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/fetch.py +0 -0
  218. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/installed.py +0 -0
  219. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/loader.py +0 -0
  220. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/plugin.py +0 -0
  221. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/source.py +0 -0
  222. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/py.typed +0 -0
  223. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/secret/__init__.py +0 -0
  224. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/secret/secrets.py +0 -0
  225. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/__init__.py +0 -0
  226. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/_shell_ast.py +0 -0
  227. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/analyzer.py +0 -0
  228. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/confirmation_policy.py +0 -0
  229. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
  230. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
  231. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
  232. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
  233. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/ensemble.py +0 -0
  234. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
  235. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
  236. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/grayswan/utils.py +0 -0
  237. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/llm_analyzer.py +0 -0
  238. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/risk.py +0 -0
  239. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/shell_parser.py +0 -0
  240. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/settings/__init__.py +0 -0
  241. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/settings/acp_providers.py +0 -0
  242. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/settings/api_models.py +0 -0
  243. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/settings/metadata.py +0 -0
  244. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/__init__.py +0 -0
  245. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/exceptions.py +0 -0
  246. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/execute.py +0 -0
  247. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/fetch.py +0 -0
  248. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/installed.py +0 -0
  249. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/skill.py +0 -0
  250. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/trigger.py +0 -0
  251. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/types.py +0 -0
  252. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/utils.py +0 -0
  253. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/subagent/registry.py +0 -0
  254. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/testing/__init__.py +0 -0
  255. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/__init__.py +0 -0
  256. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
  257. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/builtins/finish.py +0 -0
  258. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
  259. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
  260. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/builtins/think.py +0 -0
  261. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/client_tool.py +0 -0
  262. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/registry.py +0 -0
  263. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/schema.py +0 -0
  264. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/spec.py +0 -0
  265. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/tool.py +0 -0
  266. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/__init__.py +0 -0
  267. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/async_executor.py +0 -0
  268. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/async_utils.py +0 -0
  269. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/cipher.py +0 -0
  270. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/command.py +0 -0
  271. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/datetime.py +0 -0
  272. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/deprecation.py +0 -0
  273. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/github.py +0 -0
  274. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/json.py +0 -0
  275. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/models.py +0 -0
  276. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/paging.py +0 -0
  277. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/path.py +0 -0
  278. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  279. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/truncate.py +0 -0
  280. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/visualize.py +0 -0
  281. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/__init__.py +0 -0
  282. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/base.py +0 -0
  283. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/local.py +0 -0
  284. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/models.py +0 -0
  285. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
  286. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
  287. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/remote/base.py +0 -0
  288. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
  289. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/repo.py +0 -0
  290. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/workspace.py +0 -0
  291. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  292. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands_sdk.egg-info/requires.txt +0 -0
  293. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands_sdk.egg-info/top_level.txt +0 -0
  294. {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/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.30.0
4
4
  Summary: OpenHands SDK - Core functionality for building AI agents
5
5
  Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
6
6
  Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
@@ -85,6 +85,7 @@ from openhands.sdk.skills import (
85
85
  )
86
86
  from openhands.sdk.subagent import (
87
87
  agent_definition_to_factory,
88
+ discover_agents,
88
89
  load_agents_from_dir,
89
90
  load_project_agents,
90
91
  load_user_agents,
@@ -197,6 +198,7 @@ __all__ = [
197
198
  "load_project_agents",
198
199
  "load_user_agents",
199
200
  "load_agents_from_dir",
201
+ "discover_agents",
200
202
  "agent_definition_to_factory",
201
203
  "load_project_skills",
202
204
  "load_skills_from_dir",
@@ -27,7 +27,7 @@ from openhands.sdk.agent.utils import (
27
27
  parse_tool_call_arguments,
28
28
  prepare_llm_messages,
29
29
  )
30
- from openhands.sdk.context.prompts.presets import create_registry
30
+ from openhands.sdk.context.prompts.presets import PromptPreset, create_registry
31
31
  from openhands.sdk.conversation import (
32
32
  CancellationToken,
33
33
  ConversationCallbackType,
@@ -51,6 +51,7 @@ from openhands.sdk.event.condenser import (
51
51
  CondensationRequest,
52
52
  )
53
53
  from openhands.sdk.llm import (
54
+ LLM,
54
55
  LLMResponse,
55
56
  Message,
56
57
  MessageToolCall,
@@ -61,9 +62,11 @@ from openhands.sdk.llm import (
61
62
  )
62
63
  from openhands.sdk.llm.exceptions import (
63
64
  FunctionCallValidationError,
65
+ LLMContentPolicyViolationError,
64
66
  LLMContextWindowExceedError,
65
67
  LLMMalformedConversationHistoryError,
66
68
  )
69
+ from openhands.sdk.llm.router.base import RouterLLM
67
70
  from openhands.sdk.logger import get_logger
68
71
  from openhands.sdk.observability.laminar import (
69
72
  maybe_init_laminar,
@@ -78,6 +81,7 @@ from openhands.sdk.tool import (
78
81
 
79
82
 
80
83
  if TYPE_CHECKING:
84
+ from openhands.sdk.llm.llm import LLMCallContext
81
85
  from openhands.sdk.tool import ToolDefinition
82
86
  from openhands.sdk.mcp.tool import MCPToolDefinition
83
87
  from openhands.sdk.tool.builtins import (
@@ -112,6 +116,36 @@ def _tool_has_summary_param(tool: ToolDefinition) -> bool:
112
116
  INIT_STATE_PREFIX_SCAN_WINDOW = 3
113
117
 
114
118
 
119
+ def _latest_user_message_contains_image(messages: list[Message]) -> bool:
120
+ for message in reversed(messages):
121
+ if message.role == "user":
122
+ return message.contains_image
123
+ return False
124
+
125
+
126
+ def _non_multimodal_image_message(model: str) -> Message:
127
+ return Message(
128
+ role="assistant",
129
+ content=[
130
+ TextContent(
131
+ text=(
132
+ "I received your image, but the currently selected model "
133
+ f"({model}) does not support image understanding. Please "
134
+ "switch to a multimodal model to analyze the image."
135
+ )
136
+ )
137
+ ],
138
+ )
139
+
140
+
141
+ def _should_handle_non_multimodal_image_input(
142
+ llm: LLM, messages: list[Message]
143
+ ) -> bool:
144
+ if isinstance(llm, RouterLLM):
145
+ return False
146
+ return _latest_user_message_contains_image(messages) and not llm.vision_is_active()
147
+
148
+
115
149
  @dataclass(frozen=True, slots=True)
116
150
  class _ActionBatch:
117
151
  """Immutable result of preparing a batch of actions for execution.
@@ -473,7 +507,10 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
473
507
  secret_infos = state.secret_registry.get_secret_infos()
474
508
 
475
509
  ctx = self._build_prompt_context(additional_secret_infos=secret_infos)
476
- return create_registry().build(ctx).dynamic
510
+ # The dynamic tier is preset-independent; fall back to the default tier for a
511
+ # custom Jinja template (preset None), as before.
512
+ preset = self._prompt_preset or PromptPreset.DEFAULT
513
+ return create_registry(preset).build(ctx).dynamic
477
514
 
478
515
  def _execute_actions(
479
516
  self,
@@ -571,6 +608,10 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
571
608
  "skipping hook check for legacy conversation state."
572
609
  )
573
610
 
611
+ # Build per-conversation context once and thread it through all
612
+ # LLM calls in this step (avoids shared mutable state on the LLM).
613
+ call_context: LLMCallContext = conversation.get_llm_call_context()
614
+
574
615
  # Prepare LLM messages from the cached, incrementally-maintained view.
575
616
  # See https://github.com/OpenHands/software-agent-sdk/issues/3053.
576
617
  _messages_or_condensation = prepare_llm_messages(
@@ -584,6 +625,20 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
584
625
 
585
626
  _messages = _messages_or_condensation
586
627
 
628
+ if _should_handle_non_multimodal_image_input(self.llm, _messages):
629
+ logger.info(
630
+ "Image input received while selected model does not support vision: %s",
631
+ self.llm.model,
632
+ )
633
+ on_event(
634
+ MessageEvent(
635
+ source="agent",
636
+ llm_message=_non_multimodal_image_message(self.llm.model),
637
+ )
638
+ )
639
+ state.execution_status = ConversationExecutionStatus.FINISHED
640
+ return
641
+
587
642
  logger.debug(
588
643
  "Sending messages to LLM: "
589
644
  f"{json.dumps([m.model_dump() for m in _messages[1:]], indent=2)}"
@@ -595,6 +650,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
595
650
  _messages,
596
651
  tools=list(self.tools_map.values()),
597
652
  on_token=on_token,
653
+ call_context=call_context,
598
654
  )
599
655
  except FunctionCallValidationError as e:
600
656
  logger.warning(f"LLM generated malformed function call: {e}")
@@ -607,6 +663,28 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
607
663
  )
608
664
  on_event(error_message)
609
665
  return
666
+ except LLMContentPolicyViolationError as e:
667
+ # Content-policy blocks are deterministic; nudge the model and let the
668
+ # run loop continue instead of emitting a fatal error.
669
+ logger.warning(f"LLM output blocked by content filter: {e}")
670
+ on_event(
671
+ MessageEvent(
672
+ source="user",
673
+ llm_message=Message(
674
+ role="user",
675
+ content=[
676
+ TextContent(
677
+ text=(
678
+ "Your previous response was blocked by the "
679
+ "model's content filter. Please continue, "
680
+ "rephrasing to avoid the flagged content."
681
+ )
682
+ )
683
+ ],
684
+ ),
685
+ )
686
+ )
687
+ return
610
688
  except LLMMalformedConversationHistoryError as e:
611
689
  # The provider rejected the current message history as structurally
612
690
  # invalid (for example, broken tool_use/tool_result pairing). Route
@@ -712,6 +790,8 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
712
790
  "skipping hook check for legacy conversation state."
713
791
  )
714
792
 
793
+ call_context: LLMCallContext = conversation.get_llm_call_context()
794
+
715
795
  # Prepare LLM messages from the cached, incrementally-maintained view.
716
796
  # See https://github.com/OpenHands/software-agent-sdk/issues/3053.
717
797
  _messages_or_condensation = await aprepare_llm_messages(
@@ -724,6 +804,20 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
724
804
 
725
805
  _messages = _messages_or_condensation
726
806
 
807
+ if _should_handle_non_multimodal_image_input(self.llm, _messages):
808
+ logger.info(
809
+ "Image input received while selected model does not support vision: %s",
810
+ self.llm.model,
811
+ )
812
+ on_event(
813
+ MessageEvent(
814
+ source="agent",
815
+ llm_message=_non_multimodal_image_message(self.llm.model),
816
+ )
817
+ )
818
+ state.execution_status = ConversationExecutionStatus.FINISHED
819
+ return
820
+
727
821
  logger.debug(
728
822
  "Sending messages to LLM: "
729
823
  f"{json.dumps([m.model_dump() for m in _messages[1:]], indent=2)}"
@@ -735,6 +829,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
735
829
  _messages,
736
830
  tools=list(self.tools_map.values()),
737
831
  on_token=on_token,
832
+ call_context=call_context,
738
833
  )
739
834
  except FunctionCallValidationError as e:
740
835
  logger.warning(f"LLM generated malformed function call: {e}")
@@ -747,6 +842,28 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
747
842
  )
748
843
  on_event(error_message)
749
844
  return
845
+ except LLMContentPolicyViolationError as e:
846
+ # Content-policy blocks are deterministic; nudge the model and let the
847
+ # run loop continue instead of emitting a fatal error.
848
+ logger.warning(f"LLM output blocked by content filter: {e}")
849
+ on_event(
850
+ MessageEvent(
851
+ source="user",
852
+ llm_message=Message(
853
+ role="user",
854
+ content=[
855
+ TextContent(
856
+ text=(
857
+ "Your previous response was blocked by the "
858
+ "model's content filter. Please continue, "
859
+ "rephrasing to avoid the flagged content."
860
+ )
861
+ )
862
+ ],
863
+ ),
864
+ )
865
+ )
866
+ return
750
867
  except LLMMalformedConversationHistoryError as e:
751
868
  # The provider rejected the current message history as
752
869
  # 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 (
@@ -25,7 +26,7 @@ from pydantic import (
25
26
 
26
27
  from openhands.sdk.context.agent_context import AgentContext
27
28
  from openhands.sdk.context.condenser import CondenserBase
28
- from openhands.sdk.context.prompts.presets import create_registry
29
+ from openhands.sdk.context.prompts.presets import PromptPreset, create_registry
29
30
  from openhands.sdk.context.prompts.prompt import render_template
30
31
  from openhands.sdk.context.prompts.section import Platform, PromptContext
31
32
  from openhands.sdk.critic.base import CriticBase
@@ -110,12 +111,21 @@ _DEFAULT_SOUL = (
110
111
  " with a computer to solve tasks."
111
112
  )
112
113
 
113
- # Built-in prompt dir. The registry only stands in for the default prompt here; a
114
- # subclass with its own prompts/system_prompt.j2 keeps the Jinja render path.
114
+ # Built-in prompt dir. The registry only stands in for built-in prompts here; a
115
+ # subclass with its own prompts/ keeps the Jinja render path.
115
116
  _BUILTIN_PROMPT_DIR = os.path.realpath(
116
117
  os.path.join(os.path.dirname(__file__), "prompts")
117
118
  )
118
119
 
120
+ # Built-in ``system_prompt_filename`` values are back-compat sentinels (the .j2 files
121
+ # were removed) that select a registry preset. ``system_prompt_planning.j2`` keeps its
122
+ # historical name so ``get_planning_agent`` needs no change. Any other filename -- or a
123
+ # subclass's own ``prompt_dir`` -- falls through to the Jinja escape hatch.
124
+ _PRESET_BY_FILENAME: dict[str, PromptPreset] = {
125
+ "system_prompt.j2": PromptPreset.DEFAULT,
126
+ "system_prompt_planning.j2": PromptPreset.PLANNING,
127
+ }
128
+
119
129
 
120
130
  def _load_soul_md() -> str:
121
131
  """Load ``~/.openhands/SOUL.md``, falling back to the built-in default."""
@@ -248,10 +258,14 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
248
258
  security_policy_filename: str = Field(
249
259
  default="security_policy.j2",
250
260
  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"
261
+ "Security policy filename. The default 'security_policy.j2' is a "
262
+ "back-compat sentinel (the file was removed) that selects the built-in "
263
+ "default policy from the prompt registry -- it is not loaded from disk. "
264
+ "Any other value names a custom policy file whose contents are inserted "
265
+ "verbatim (NOT rendered as a Jinja template). Can be either:\n"
266
+ "- A relative filename (e.g., 'custom_security_policy.md') loaded from "
267
+ "the agent's prompts directory\n"
268
+ "- An absolute path (e.g., '/path/to/custom_security_policy.md')\n"
255
269
  "- Empty string to disable security policy"
256
270
  ),
257
271
  )
@@ -456,6 +470,18 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
456
470
  """Returns the name of the Agent."""
457
471
  return self.__class__.__name__
458
472
 
473
+ @property
474
+ def _prompt_preset(self) -> PromptPreset | None:
475
+ """The registry preset for this agent's built-in prompt.
476
+
477
+ ``None`` means "take the Jinja escape hatch": a subclass with its own
478
+ ``prompt_dir``, or a ``system_prompt_filename`` that is not a known built-in
479
+ sentinel (e.g. a custom relative name or an absolute path).
480
+ """
481
+ if os.path.realpath(self.prompt_dir) != _BUILTIN_PROMPT_DIR:
482
+ return None
483
+ return _PRESET_BY_FILENAME.get(self.system_prompt_filename)
484
+
459
485
  @property
460
486
  def static_system_message(self) -> str:
461
487
  """Compute the static portion of the system message.
@@ -464,12 +490,11 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
464
490
  per-conversation context. This static portion can be cached and reused
465
491
  across conversations for better prompt caching efficiency.
466
492
 
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.
493
+ Built-in prompts (the ``default`` and ``planning`` presets) are assembled from
494
+ the typed section registry, which also resolves a custom
495
+ ``security_policy_filename``. Escape hatches keep the Jinja path: an inline
496
+ ``system_prompt`` is returned verbatim; a custom ``system_prompt_filename`` or
497
+ subclass ``prompt_dir`` renders its own template.
473
498
 
474
499
  Returns:
475
500
  The static system prompt without dynamic context.
@@ -477,22 +502,17 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
477
502
  if self.system_prompt is not None:
478
503
  return self.system_prompt
479
504
 
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.
484
- if (
485
- self.system_prompt_filename != "system_prompt.j2"
486
- or os.path.realpath(self.prompt_dir) != _BUILTIN_PROMPT_DIR
487
- or self.security_policy_filename != "security_policy.j2"
488
- ):
505
+ # Escape hatch: a custom filename or a subclass's own prompt_dir renders its
506
+ # own Jinja template; everything else (incl. custom policies) uses the registry.
507
+ preset = self._prompt_preset
508
+ if preset is None:
489
509
  return render_template(
490
510
  prompt_dir=self.prompt_dir,
491
511
  template_name=self.system_prompt_filename,
492
512
  **self._resolved_template_kwargs(),
493
513
  )
494
514
 
495
- return create_registry().build(self._build_prompt_context()).static
515
+ return create_registry(preset).build(self._build_prompt_context()).static
496
516
 
497
517
  def _resolved_template_kwargs(self) -> dict[str, object]:
498
518
  """Resolve the system-prompt template kwargs.
@@ -525,6 +545,24 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
525
545
  template_kwargs["model_variant"] = spec.variant
526
546
  return template_kwargs
527
547
 
548
+ def _read_custom_security_policy(self) -> str | None:
549
+ """Raw contents of a custom security policy file -- inserted verbatim, NOT
550
+ rendered as a Jinja template.
551
+
552
+ Returns ``None`` -- so ``SecuritySection`` keeps its built-in default policy
553
+ -- when ``security_policy_filename`` is the default sentinel
554
+ ``"security_policy.j2"`` (a string only; the file was removed, so it is never
555
+ read) or ``""`` (an empty *filename*, which disables the policy). A configured
556
+ file whose own contents are empty still returns ``""`` (an empty custom
557
+ policy), not ``None``.
558
+
559
+ Relative names resolve against ``prompt_dir``; absolute paths are used as-is.
560
+ """
561
+ filename = self.security_policy_filename
562
+ if not filename or filename == "security_policy.j2":
563
+ return None
564
+ return (Path(self.prompt_dir) / filename).read_text(encoding="utf-8")
565
+
528
566
  def _build_prompt_context(
529
567
  self,
530
568
  additional_secret_infos: list[dict[str, str | None]] | None = None,
@@ -578,8 +616,17 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
578
616
  # secrets (additional_secret_infos), matching what <CUSTOM_SECRETS> shows.
579
617
  secret_names = tuple(name for name, _ in secret_infos if name)
580
618
 
619
+ template_kwargs = self._resolved_template_kwargs()
620
+ # A custom security policy's content for SecuritySection (registry path only).
621
+ policy_content = self._read_custom_security_policy()
622
+ if policy_content is not None:
623
+ template_kwargs = {
624
+ **template_kwargs,
625
+ "security_policy_content": policy_content,
626
+ }
627
+
581
628
  return PromptContext(
582
- template_kwargs=self._resolved_template_kwargs(),
629
+ template_kwargs=template_kwargs,
583
630
  tool_names=tuple(t.name for t in self.tools),
584
631
  platform=Platform.current(),
585
632
  working_dir=None,
@@ -613,7 +660,10 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
613
660
  """
614
661
  if not self.agent_context:
615
662
  return None
616
- return create_registry().build(self._build_prompt_context()).dynamic
663
+ # The dynamic tier is preset-independent, so a custom Jinja template (preset
664
+ # None) still gets the default dynamic block, exactly as before.
665
+ preset = self._prompt_preset or PromptPreset.DEFAULT
666
+ return create_registry(preset).build(self._build_prompt_context()).dynamic
617
667
 
618
668
  def init_state(
619
669
  self,
@@ -31,6 +31,7 @@ from openhands.sdk.tool import Action, ToolDefinition
31
31
 
32
32
 
33
33
  if TYPE_CHECKING:
34
+ from openhands.sdk.llm.llm import LLMCallContext
34
35
  from openhands.sdk.llm.streaming import AnyTokenCallbackType
35
36
 
36
37
 
@@ -604,6 +605,7 @@ def make_llm_completion(
604
605
  messages: list[Message],
605
606
  tools: list[ToolDefinition] | None = None,
606
607
  on_token: ConversationTokenCallbackType | None = None,
608
+ call_context: LLMCallContext | None = None,
607
609
  ) -> LLMResponse:
608
610
  """Make an LLM completion call with the provided messages and tools.
609
611
 
@@ -612,6 +614,7 @@ def make_llm_completion(
612
614
  messages: The messages to send to the LLM
613
615
  tools: Optional list of tools to provide to the LLM
614
616
  on_token: Optional callback for streaming token updates
617
+ call_context: Per-conversation context for cache/session affinity.
615
618
 
616
619
  Returns:
617
620
  LLMResponse from the LLM completion call
@@ -636,6 +639,7 @@ def make_llm_completion(
636
639
  store=False,
637
640
  add_security_risk_prediction=True,
638
641
  on_token=on_token,
642
+ call_context=call_context,
639
643
  )
640
644
  else:
641
645
  return llm.completion(
@@ -643,6 +647,7 @@ def make_llm_completion(
643
647
  tools=tools or [],
644
648
  add_security_risk_prediction=True,
645
649
  on_token=on_token,
650
+ call_context=call_context,
646
651
  )
647
652
 
648
653
 
@@ -686,6 +691,7 @@ async def amake_llm_completion(
686
691
  messages: list[Message],
687
692
  tools: list[ToolDefinition] | None = None,
688
693
  on_token: AnyTokenCallbackType | None = None,
694
+ call_context: LLMCallContext | None = None,
689
695
  ) -> LLMResponse:
690
696
  """Async variant of :func:`make_llm_completion`."""
691
697
  if llm.uses_responses_api():
@@ -696,6 +702,7 @@ async def amake_llm_completion(
696
702
  store=False,
697
703
  add_security_risk_prediction=True,
698
704
  on_token=on_token,
705
+ call_context=call_context,
699
706
  )
700
707
  else:
701
708
  return await llm.acompletion(
@@ -703,4 +710,5 @@ async def amake_llm_completion(
703
710
  tools=tools or [],
704
711
  add_security_risk_prediction=True,
705
712
  on_token=on_token,
713
+ call_context=call_context,
706
714
  )
@@ -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
@@ -116,7 +118,8 @@ class AgentContext(BaseModel):
116
118
  default_factory=list,
117
119
  description=(
118
120
  "Marketplace registrations for plugin resolution. Registrations with "
119
- "auto_load=True are resolved by LocalConversation at startup."
121
+ "auto_load=True or a list of plugin names are resolved by "
122
+ "LocalConversation at startup."
120
123
  ),
121
124
  json_schema_extra={"acp_compatible": True},
122
125
  )
@@ -147,8 +150,8 @@ class AgentContext(BaseModel):
147
150
  json_schema_extra={"acp_compatible": True},
148
151
  )
149
152
  current_datetime: datetime | str | None = Field(
150
- # Timezone-aware local "now" so the value injected into the system prompt
151
- # carries a UTC offset instead of an ambiguous naive local time (#3438).
153
+ # Timezone-aware local "now"; get_formatted_datetime renders it to the
154
+ # minute for the prompt.
152
155
  default_factory=lambda: datetime.now().astimezone(),
153
156
  description=(
154
157
  "Current date and time information to provide to the agent. "
@@ -263,13 +266,17 @@ class AgentContext(BaseModel):
263
266
 
264
267
  Returns:
265
268
  Formatted datetime string, or None if current_datetime is not set.
266
- If current_datetime is a datetime object, it's formatted as ISO 8601.
269
+ If current_datetime is a datetime object, it's formatted as
270
+ "YYYY-MM-DDTHH:MM" (no seconds, microseconds, or UTC offset).
267
271
  If current_datetime is already a string, it's returned as-is.
268
272
  """
269
273
  if self.current_datetime is None:
270
274
  return None
271
275
  if isinstance(self.current_datetime, datetime):
272
- return self.current_datetime.isoformat()
276
+ # Local wall-clock to the minute: drop seconds, microseconds, offset.
277
+ return self.current_datetime.replace(tzinfo=None).isoformat(
278
+ timespec="minutes"
279
+ )
273
280
  return self.current_datetime
274
281
 
275
282
  def _partition_skills(self) -> tuple[list[Skill], list[Skill]]:
@@ -327,27 +334,16 @@ class AgentContext(BaseModel):
327
334
  data = self._resolve_dynamic_data(
328
335
  llm_model, llm_model_canonical, additional_secret_infos
329
336
  )
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
337
+ ctx = PromptContext(
338
+ now=data.formatted_datetime,
339
+ repo_skills=tuple((s.name, s.content) for s in data.repo_skills),
340
+ available_skills_prompt=data.available_skills_prompt or None,
341
+ custom_suffix=self.system_message_suffix or None,
342
+ secret_infos=tuple(
343
+ (info["name"] or "", info["description"]) for info in data.secret_infos
344
+ ),
336
345
  )
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
346
+ return create_registry().build(ctx).dynamic
351
347
 
352
348
  def _resolve_dynamic_data(
353
349
  self,
@@ -433,9 +429,10 @@ class AgentContext(BaseModel):
433
429
  this adapter only emits prompt-only context. Unsupported AgentContext
434
430
  fields are rejected by :meth:`validate_acp_compatibility`.
435
431
 
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
432
+ The rendering reuses :meth:`get_system_message_suffix`, which assembles
433
+ the dynamic-tier sections via the shared prompt registry, so that ACP
434
+ agents receive the identical prompt layout as the regular agent. This
435
+ includes the
439
436
  ``<CUSTOM_SECRETS>`` block when secrets are present, informing the ACP
440
437
  subprocess which environment variables are available. The actual secret
441
438
  values are injected into the subprocess environment by
@@ -455,7 +452,8 @@ class AgentContext(BaseModel):
455
452
  """
456
453
  self.validate_acp_compatibility()
457
454
  # No model-specific skill filtering for ACP — delegate to the shared
458
- # renderer which also renders the <CUSTOM_SECRETS> block from secrets.
455
+ # builder, whose dynamic-tier sections also emit the <CUSTOM_SECRETS>
456
+ # block from secrets.
459
457
  return self.get_system_message_suffix(
460
458
  additional_secret_infos=additional_secret_infos
461
459
  )
@@ -80,6 +80,16 @@ class LLMSummarizingCondenser(RollingCondenser):
80
80
  )
81
81
  return self
82
82
 
83
+ @model_validator(mode="after")
84
+ def _disable_streaming_for_summary(self):
85
+ # Summaries are consumed whole with no on_token callback, which a
86
+ # streaming LLM requires. Disable streaming once so every summary path
87
+ # is covered. model_copy is non-mutating and shares usage_id/metrics,
88
+ # so summary tokens stay attributed to the conversation.
89
+ if self.llm.stream:
90
+ self.llm = self.llm.model_copy(update={"stream": False})
91
+ return self
92
+
83
93
  def handles_condensation_requests(self) -> bool:
84
94
  return True
85
95