openhands-sdk 1.50.1__tar.gz → 1.52.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 (320) hide show
  1. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/PKG-INFO +3 -3
  2. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/agent.py +18 -1
  3. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/base.py +40 -8
  4. openhands_sdk-1.52.0/openhands/sdk/automation/__init__.py +215 -0
  5. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/presets.py +44 -16
  6. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/section.py +16 -11
  7. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/sections/__init__.py +4 -2
  8. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/sections/static.py +22 -14
  9. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/base.py +16 -1
  10. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/conversation.py +5 -0
  11. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/impl/local_conversation.py +2 -0
  12. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/impl/remote_conversation.py +40 -4
  13. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/request.py +43 -33
  14. openhands_sdk-1.52.0/openhands/sdk/launch/__init__.py +42 -0
  15. openhands_sdk-1.52.0/openhands/sdk/launch/errors.py +65 -0
  16. openhands_sdk-1.52.0/openhands/sdk/launch/finalize.py +214 -0
  17. openhands_sdk-1.52.0/openhands/sdk/launch/preview.py +115 -0
  18. openhands_sdk-1.52.0/openhands/sdk/launch/resolve.py +239 -0
  19. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/llm_profile_store.py +24 -2
  20. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/model_features.py +80 -3
  21. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/verified_models.py +32 -0
  22. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/observability/laminar.py +118 -3
  23. openhands_sdk-1.52.0/openhands/sdk/observability/utils.py +26 -0
  24. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/profiles/agent_profile.py +64 -11
  25. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/profiles/resolver.py +95 -7
  26. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/profiles/seed.py +5 -3
  27. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/settings/model.py +175 -95
  28. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/subagent/__init__.py +2 -0
  29. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/subagent/registry.py +8 -2
  30. openhands_sdk-1.52.0/openhands/sdk/subagent/scope.py +81 -0
  31. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/__init__.py +10 -0
  32. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/__init__.py +15 -0
  33. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/classify_and_switch_llm.py +51 -31
  34. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/finish.py +3 -1
  35. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/invoke_skill.py +3 -1
  36. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/switch_llm.py +5 -1
  37. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/think.py +3 -1
  38. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/vision_inspect.py +2 -0
  39. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/client_tool.py +3 -1
  40. openhands_sdk-1.52.0/openhands/sdk/tool/defaults.py +212 -0
  41. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/registry.py +111 -13
  42. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/tool.py +13 -0
  43. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/workspace/base.py +2 -1
  44. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +8 -0
  45. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/workspace/remote/base.py +16 -0
  46. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +17 -1
  47. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands_sdk.egg-info/PKG-INFO +3 -3
  48. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands_sdk.egg-info/SOURCES.txt +14 -0
  49. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands_sdk.egg-info/requires.txt +2 -2
  50. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/pyproject.toml +3 -3
  51. openhands_sdk-1.50.1/openhands/sdk/observability/utils.py +0 -20
  52. openhands_sdk-1.50.1/openhands/sdk/tool/defaults.py +0 -55
  53. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/__init__.py +0 -0
  54. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/__init__.py +0 -0
  55. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/acp_agent.py +0 -0
  56. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/acp_file_credentials.py +0 -0
  57. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/acp_models.py +0 -0
  58. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/acp_tracing.py +0 -0
  59. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/critic_mixin.py +0 -0
  60. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/parallel_executor.py +0 -0
  61. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/response_dispatch.py +0 -0
  62. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/stream_context.py +0 -0
  63. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/agent/utils.py +0 -0
  64. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/banner.py +0 -0
  65. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/__init__.py +0 -0
  66. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/agent_context.py +0 -0
  67. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/__init__.py +0 -0
  68. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/base.py +0 -0
  69. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
  70. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  71. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  72. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/prompts/summarizing_events.j2 +0 -0
  73. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/prompts/summarizing_system.j2 +0 -0
  74. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/utils.py +0 -0
  75. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/memory.py +0 -0
  76. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/__init__.py +0 -0
  77. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/prompt.py +0 -0
  78. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/registry.py +0 -0
  79. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
  80. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
  81. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  82. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  83. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/view/__init__.py +0 -0
  84. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
  85. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
  86. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/base.py +0 -0
  87. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
  88. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
  89. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
  90. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
  91. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/context/view/view.py +0 -0
  92. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/__init__.py +0 -0
  93. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/cancellation.py +0 -0
  94. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
  95. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/event_store.py +0 -0
  96. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/events_list_base.py +0 -0
  97. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/exceptions.py +0 -0
  98. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
  99. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
  100. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/goal/controller.py +0 -0
  101. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/goal/judge.py +0 -0
  102. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
  103. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/goal/runner.py +0 -0
  104. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
  105. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/persistence_const.py +0 -0
  106. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
  107. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/response_utils.py +0 -0
  108. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/secret_registry.py +0 -0
  109. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/state.py +0 -0
  110. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
  111. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/title_utils.py +0 -0
  112. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/types.py +0 -0
  113. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  114. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
  115. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
  116. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/credential.py +0 -0
  117. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/__init__.py +0 -0
  118. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/base.py +0 -0
  119. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/__init__.py +0 -0
  120. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  121. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  122. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  123. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/api/client.py +0 -0
  124. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
  125. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  126. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  127. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  128. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/critic/result.py +0 -0
  129. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/__init__.py +0 -0
  130. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/acp_tool_call.py +0 -0
  131. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/base.py +0 -0
  132. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/condenser.py +0 -0
  133. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/conversation_error.py +0 -0
  134. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/conversation_state.py +0 -0
  135. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/error_classification.py +0 -0
  136. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/hook_execution.py +0 -0
  137. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_completion_log.py +0 -0
  138. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  139. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
  140. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
  141. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  142. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
  143. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
  144. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/resume_transcript.py +0 -0
  145. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/streaming_delta.py +0 -0
  146. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/token.py +0 -0
  147. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/types.py +0 -0
  148. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/event/user_action.py +0 -0
  149. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/extensions/__init__.py +0 -0
  150. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/extensions/fetch.py +0 -0
  151. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
  152. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/info.py +0 -0
  153. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/interface.py +0 -0
  154. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/manager.py +0 -0
  155. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
  156. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/utils.py +0 -0
  157. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/git/cached_repo.py +0 -0
  158. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/git/exceptions.py +0 -0
  159. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/git/git_changes.py +0 -0
  160. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/git/git_commits.py +0 -0
  161. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/git/git_diff.py +0 -0
  162. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/git/models.py +0 -0
  163. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/git/utils.py +0 -0
  164. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/hooks/__init__.py +0 -0
  165. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/hooks/config.py +0 -0
  166. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  167. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/hooks/executor.py +0 -0
  168. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/hooks/manager.py +0 -0
  169. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/hooks/types.py +0 -0
  170. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/io/__init__.py +0 -0
  171. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/io/base.py +0 -0
  172. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/io/cache.py +0 -0
  173. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/io/local.py +0 -0
  174. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/io/memory.py +0 -0
  175. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/__init__.py +0 -0
  176. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/auth/__init__.py +0 -0
  177. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/auth/credentials.py +0 -0
  178. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/auth/openai.py +0 -0
  179. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/call_context.py +0 -0
  180. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
  181. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
  182. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/exceptions/classifier.py +0 -0
  183. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
  184. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/exceptions/types.py +0 -0
  185. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
  186. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/llm.py +0 -0
  187. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/llm_registry.py +0 -0
  188. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/llm_response.py +0 -0
  189. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/message.py +0 -0
  190. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/meta_profile_store.py +0 -0
  191. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  192. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
  193. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  194. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/options/__init__.py +0 -0
  195. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/options/chat_options.py +0 -0
  196. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/options/common.py +0 -0
  197. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/options/responses_options.py +0 -0
  198. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
  199. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/router/__init__.py +0 -0
  200. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/router/base.py +0 -0
  201. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  202. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/router/impl/random.py +0 -0
  203. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/streaming.py +0 -0
  204. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
  205. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
  206. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
  207. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/metrics.py +0 -0
  208. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/model_info.py +0 -0
  209. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  210. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
  211. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
  212. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
  213. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
  214. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
  215. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
  216. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
  217. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  218. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
  219. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/logger/__init__.py +0 -0
  220. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/logger/logger.py +0 -0
  221. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/logger/rolling.py +0 -0
  222. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/marketplace/__init__.py +0 -0
  223. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/marketplace/registration.py +0 -0
  224. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/marketplace/registry.py +0 -0
  225. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/marketplace/types.py +0 -0
  226. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/mcp/__init__.py +0 -0
  227. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/mcp/client.py +0 -0
  228. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/mcp/config.py +0 -0
  229. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/mcp/definition.py +0 -0
  230. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/mcp/exceptions.py +0 -0
  231. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/mcp/oauth.py +0 -0
  232. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/mcp/tool.py +0 -0
  233. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/mcp/utils.py +0 -0
  234. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/observability/__init__.py +0 -0
  235. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/__init__.py +0 -0
  236. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/discovery.py +0 -0
  237. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/fetch.py +0 -0
  238. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/__init__.py +0 -0
  239. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
  240. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
  241. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/base.py +0 -0
  242. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
  243. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
  244. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
  245. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/installed.py +0 -0
  246. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/loader.py +0 -0
  247. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/plugin.py +0 -0
  248. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/source.py +0 -0
  249. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/plugin/types.py +0 -0
  250. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/profiles/__init__.py +0 -0
  251. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
  252. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/profiles/profile_refs.py +0 -0
  253. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/py.typed +0 -0
  254. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/secret/__init__.py +0 -0
  255. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/secret/secrets.py +0 -0
  256. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/__init__.py +0 -0
  257. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/_shell_ast.py +0 -0
  258. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/analyzer.py +0 -0
  259. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/confirmation_policy.py +0 -0
  260. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
  261. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
  262. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
  263. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
  264. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
  265. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/ensemble.py +0 -0
  266. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
  267. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
  268. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/grayswan/utils.py +0 -0
  269. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/llm_analyzer.py +0 -0
  270. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/risk.py +0 -0
  271. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/shell_parser.py +0 -0
  272. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
  273. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
  274. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/settings/__init__.py +0 -0
  275. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/settings/acp_install_catalog.py +0 -0
  276. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/settings/acp_providers.py +0 -0
  277. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/settings/api_models.py +0 -0
  278. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/settings/metadata.py +0 -0
  279. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/skills/__init__.py +0 -0
  280. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/skills/exceptions.py +0 -0
  281. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/skills/execute.py +0 -0
  282. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/skills/fetch.py +0 -0
  283. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/skills/installed.py +0 -0
  284. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/skills/skill.py +0 -0
  285. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/skills/trigger.py +0 -0
  286. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/skills/types.py +0 -0
  287. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/skills/utils.py +0 -0
  288. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/subagent/load.py +0 -0
  289. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/subagent/schema.py +0 -0
  290. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/testing/__init__.py +0 -0
  291. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/testing/test_llm.py +0 -0
  292. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/schema.py +0 -0
  293. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/tool/spec.py +0 -0
  294. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/__init__.py +0 -0
  295. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/async_executor.py +0 -0
  296. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/async_utils.py +0 -0
  297. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/cipher.py +0 -0
  298. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/command.py +0 -0
  299. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/datetime.py +0 -0
  300. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/deprecation.py +0 -0
  301. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/files.py +0 -0
  302. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/github.py +0 -0
  303. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/json.py +0 -0
  304. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/masking.py +0 -0
  305. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/models.py +0 -0
  306. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/paging.py +0 -0
  307. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/path.py +0 -0
  308. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  309. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/redact.py +0 -0
  310. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/truncate.py +0 -0
  311. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/utils/visualize.py +0 -0
  312. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/workspace/__init__.py +0 -0
  313. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/workspace/local.py +0 -0
  314. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/workspace/models.py +0 -0
  315. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
  316. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/workspace/repo.py +0 -0
  317. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands/sdk/workspace/workspace.py +0 -0
  318. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  319. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/openhands_sdk.egg-info/top_level.txt +0 -0
  320. {openhands_sdk-1.50.1 → openhands_sdk-1.52.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.50.1
3
+ Version: 1.52.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
@@ -17,8 +17,8 @@ Requires-Dist: jsonschema>=4.23.0
17
17
  Requires-Dist: joserfc>=1.7.5
18
18
  Requires-Dist: litellm>=1.93.0
19
19
  Requires-Dist: pillow>=12.3.0
20
- Requires-Dist: pydantic>=2.12.5
21
- Requires-Dist: python-frontmatter>=1.1.0
20
+ Requires-Dist: pydantic>=2.13.5
21
+ Requires-Dist: python-frontmatter>=1.3.0
22
22
  Requires-Dist: python-json-logger>=3.3.0
23
23
  Requires-Dist: tenacity>=9.1.2
24
24
  Requires-Dist: websockets>=12
@@ -7,7 +7,7 @@ from collections.abc import Callable
7
7
  from dataclasses import dataclass, field
8
8
  from typing import TYPE_CHECKING, Any
9
9
 
10
- from pydantic import PrivateAttr, ValidationError, model_validator
10
+ from pydantic import Field, PrivateAttr, ValidationError, model_validator
11
11
 
12
12
  import openhands.sdk.security.analyzer as analyzer
13
13
  import openhands.sdk.security.risk as risk
@@ -452,6 +452,17 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
452
452
  )
453
453
  """
454
454
 
455
+ persona: str | None = Field(
456
+ default=None,
457
+ description=(
458
+ "Persona text that replaces OpenHands' built-in persona and "
459
+ "coding-workflow sections of the system prompt. Capability and policy "
460
+ "sections (memory, security policy, risk assessment, browser, external "
461
+ "services, process management, model-specific guidance) and the dynamic "
462
+ "context still apply. Ignored when `system_prompt` is set; a custom "
463
+ "template receives it as the `persona` kwarg."
464
+ ),
465
+ )
455
466
  _parallel_executor: ParallelToolExecutor = PrivateAttr(
456
467
  default_factory=ParallelToolExecutor
457
468
  )
@@ -462,6 +473,12 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
462
473
  max_workers=self.tool_concurrency_limit
463
474
  )
464
475
 
476
+ def _resolved_template_kwargs(self) -> dict[str, object]:
477
+ template_kwargs = super()._resolved_template_kwargs()
478
+ if self.persona is not None:
479
+ template_kwargs["persona"] = self.persona
480
+ return template_kwargs
481
+
465
482
  @model_validator(mode="before")
466
483
  @classmethod
467
484
  def _add_security_prompt_as_default(cls, data):
@@ -16,6 +16,7 @@ from pydantic import (
16
16
  ConfigDict,
17
17
  Field,
18
18
  PrivateAttr,
19
+ field_validator,
19
20
  model_validator,
20
21
  )
21
22
 
@@ -32,6 +33,7 @@ from openhands.sdk.mcp.client import MCPClient
32
33
  from openhands.sdk.mcp.config import MCPServer
33
34
  from openhands.sdk.mcp.tool import MCPToolDefinition, MCPToolExecutor
34
35
  from openhands.sdk.tool import (
36
+ BROWSER_TOOL_NAME,
35
37
  BUILT_IN_TOOL_CLASSES,
36
38
  BUILT_IN_TOOLS,
37
39
  Tool,
@@ -43,6 +45,7 @@ from openhands.sdk.tool.builtins.vision_inspect import (
43
45
  VisionInspectTool,
44
46
  has_vision_profile_available,
45
47
  )
48
+ from openhands.sdk.utils.deprecation import warn_deprecated
46
49
  from openhands.sdk.utils.models import DiscriminatedUnionMixin
47
50
  from openhands.sdk.utils.path import get_user_persistence_dir
48
51
 
@@ -263,6 +266,24 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
263
266
  )
264
267
  return data
265
268
 
269
+ @field_validator("system_prompt_kwargs")
270
+ @classmethod
271
+ def _warn_deprecated_enable_browser(
272
+ cls, value: dict[str, object]
273
+ ) -> dict[str, object]:
274
+ if "enable_browser" in value:
275
+ warn_deprecated(
276
+ "system_prompt_kwargs['enable_browser']",
277
+ deprecated_in="1.51.0",
278
+ removed_in="1.56.0",
279
+ details=(
280
+ "Browser guidance now comes from the loaded browser tools; "
281
+ "only custom Jinja templates still read this kwarg."
282
+ ),
283
+ stacklevel=5,
284
+ )
285
+ return value
286
+
266
287
  condenser: CondenserBase | None = Field(
267
288
  default=None,
268
289
  description="Optional condenser to use for condensing conversation history.",
@@ -343,27 +364,41 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
343
364
  Built-in prompts (the ``default`` and ``planning`` presets) are assembled from
344
365
  the typed section registry, which also resolves a custom
345
366
  ``security_policy_filename``. Escape hatches keep the Jinja path: an inline
346
- ``system_prompt`` is returned verbatim; a custom ``system_prompt_filename`` or
347
- subclass ``prompt_dir`` renders its own template.
367
+ ``system_prompt`` is used verbatim, followed by the loaded tools' guidance; a
368
+ custom ``system_prompt_filename`` or subclass ``prompt_dir`` renders its own
369
+ template.
348
370
 
349
371
  Returns:
350
372
  The static system prompt without dynamic context.
351
373
  """
352
374
  if self.system_prompt is not None:
353
- return self.system_prompt
375
+ return "\n\n".join((self.system_prompt, *self._tool_guidance()))
354
376
 
355
377
  # Escape hatch: a custom filename or a subclass's own prompt_dir renders its
356
378
  # own Jinja template; everything else (incl. custom policies) uses the registry.
357
379
  preset = self._prompt_preset
358
380
  if preset is None:
381
+ # Deprecated kwarg, kept until 1.56.0 for templates that branch on it.
382
+ template_kwargs = {
383
+ "enable_browser": any(t.name == BROWSER_TOOL_NAME for t in self.tools),
384
+ **self._resolved_template_kwargs(),
385
+ }
359
386
  return render_template(
360
387
  prompt_dir=self.prompt_dir,
361
388
  template_name=self.system_prompt_filename,
362
- **self._resolved_template_kwargs(),
389
+ **template_kwargs,
363
390
  )
364
391
 
365
392
  return create_registry(preset).build(self._build_prompt_context()).static
366
393
 
394
+ def _tool_guidance(self) -> tuple[str, ...]:
395
+ """Distinct ``prompt_guidance`` texts of the loaded tools, in tool order."""
396
+ with self._tools_lock:
397
+ tools = list(self._tools.values())
398
+ return tuple(
399
+ dict.fromkeys(t.prompt_guidance for t in tools if t.prompt_guidance)
400
+ )
401
+
367
402
  def _resolved_template_kwargs(self) -> dict[str, object]:
368
403
  """Resolve the system-prompt template kwargs.
369
404
 
@@ -376,10 +411,6 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
376
411
  if "soul_content" not in template_kwargs:
377
412
  template_kwargs["soul_content"] = _load_soul_md()
378
413
 
379
- template_kwargs.setdefault(
380
- "enable_browser",
381
- any(t.name == "browser_tool_set" for t in self.tools),
382
- )
383
414
  template_kwargs.setdefault(
384
415
  "memory_enabled",
385
416
  self.agent_context is not None and self.agent_context.load_memory,
@@ -484,6 +515,7 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
484
515
  return PromptContext(
485
516
  template_kwargs=template_kwargs,
486
517
  tool_names=tuple(t.name for t in self.tools),
518
+ tool_guidance=self._tool_guidance(),
487
519
  platform=Platform.current(),
488
520
  working_dir=None,
489
521
  now=now,
@@ -0,0 +1,215 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from collections.abc import Mapping, Sequence
5
+ from typing import NotRequired, TypedDict
6
+
7
+ from openhands.sdk.conversation.types import TraceMetadataValue
8
+ from openhands.sdk.logger import get_logger
9
+ from openhands.sdk.observability.laminar import (
10
+ default_observability_span_name_from_env,
11
+ observability_metadata_from_env,
12
+ observability_parent_span_context_from_env,
13
+ observability_tags_from_env,
14
+ )
15
+
16
+
17
+ logger = get_logger(__name__)
18
+
19
+ _AUTOMATION_ID_KEY = "automation.id"
20
+ _AUTOMATION_NAME_KEY = "automation.name"
21
+ _AUTOMATION_RUN_ID_KEY = "automation.run_id"
22
+ _AUTOMATION_TRIGGER_SOURCE_KEY = "automation.trigger_source"
23
+ _DEFAULT_AUTOMATION_SPAN_NAME = "automation.conversation"
24
+
25
+
26
+ class AutomationObservabilityContext(TypedDict):
27
+ observability_metadata: NotRequired[dict[str, TraceMetadataValue]]
28
+ observability_tags: NotRequired[list[str]]
29
+ observability_span_name: NotRequired[str]
30
+ observability_parent_span_context: NotRequired[str]
31
+
32
+
33
+ class AutomationConversationKwargs(AutomationObservabilityContext, total=False):
34
+ tags: dict[str, str]
35
+
36
+
37
+ def _merge_metadata(
38
+ metadata: Mapping[str, TraceMetadataValue] | None,
39
+ ) -> dict[str, TraceMetadataValue]:
40
+ merged = observability_metadata_from_env()
41
+ if metadata:
42
+ merged.update(dict(metadata))
43
+ return merged
44
+
45
+
46
+ def _merge_tags(tags: Sequence[str] | None) -> list[str]:
47
+ merged: list[str] = []
48
+ seen: set[str] = set()
49
+ for tag in [*observability_tags_from_env(), *(tags or [])]:
50
+ tag = tag.strip()
51
+ if tag and tag not in seen:
52
+ seen.add(tag)
53
+ merged.append(tag)
54
+ return merged
55
+
56
+
57
+ def _metadata_str(metadata: Mapping[str, TraceMetadataValue], key: str) -> str | None:
58
+ value = metadata.get(key)
59
+ if value is None or isinstance(value, bool | list):
60
+ return None
61
+ return str(value)
62
+
63
+
64
+ def automation_observability_context(
65
+ *,
66
+ metadata: Mapping[str, TraceMetadataValue] | None = None,
67
+ tags: Sequence[str] | None = None,
68
+ span_name: str | None = None,
69
+ parent_span_context: str | None = None,
70
+ ) -> AutomationObservabilityContext:
71
+ """Collect generic automation observability fields for SDK conversations.
72
+
73
+ The automation service owns automation-specific values and supplies them via
74
+ generic ``OPENHANDS_OBSERVABILITY_*`` environment variables. This helper
75
+ collects those generic values and merges caller-provided additions, so custom
76
+ automation scripts do not need to duplicate the boilerplate for every
77
+ ``Conversation(...)`` call.
78
+ """
79
+ context: AutomationObservabilityContext = {}
80
+
81
+ merged_metadata = _merge_metadata(metadata)
82
+ if merged_metadata:
83
+ context["observability_metadata"] = merged_metadata
84
+
85
+ merged_tags = _merge_tags(tags)
86
+ if merged_tags:
87
+ context["observability_tags"] = merged_tags
88
+
89
+ resolved_span_name = span_name or default_observability_span_name_from_env()
90
+ if resolved_span_name:
91
+ context["observability_span_name"] = resolved_span_name
92
+
93
+ resolved_parent_context = (
94
+ parent_span_context or observability_parent_span_context_from_env()
95
+ )
96
+ if resolved_parent_context:
97
+ context["observability_parent_span_context"] = resolved_parent_context
98
+
99
+ return context
100
+
101
+
102
+ def automation_conversation_tags(
103
+ metadata: Mapping[str, TraceMetadataValue] | None = None,
104
+ ) -> dict[str, str]:
105
+ """Build product conversation tags from automation observability metadata."""
106
+ merged_metadata = _merge_metadata(metadata)
107
+ tags: dict[str, str] = {}
108
+
109
+ automation_id = _metadata_str(merged_metadata, _AUTOMATION_ID_KEY)
110
+ automation_name = _metadata_str(merged_metadata, _AUTOMATION_NAME_KEY)
111
+ automation_run_id = _metadata_str(merged_metadata, _AUTOMATION_RUN_ID_KEY)
112
+ trigger_source = _metadata_str(merged_metadata, _AUTOMATION_TRIGGER_SOURCE_KEY)
113
+
114
+ if automation_id or automation_name or automation_run_id or trigger_source:
115
+ tags["automationtrigger"] = "automation"
116
+ if automation_id:
117
+ tags["automationid"] = automation_id
118
+ if automation_name:
119
+ tags["automationname"] = automation_name
120
+ if automation_run_id:
121
+ tags["automationrunid"] = automation_run_id
122
+
123
+ return tags
124
+
125
+
126
+ def automation_conversation_kwargs(
127
+ *,
128
+ metadata: Mapping[str, TraceMetadataValue] | None = None,
129
+ observability_tags: Sequence[str] | None = None,
130
+ conversation_tags: Mapping[str, str] | None = None,
131
+ span_name: str | None = None,
132
+ parent_span_context: str | None = None,
133
+ include_conversation_tags: bool = True,
134
+ ) -> AutomationConversationKwargs:
135
+ """Return kwargs to pass into ``Conversation(...)`` from an automation script.
136
+
137
+ Example:
138
+ ``Conversation(..., **automation_conversation_kwargs())``
139
+ """
140
+ kwargs: AutomationConversationKwargs = {}
141
+ resolved_span_name = (
142
+ span_name
143
+ or default_observability_span_name_from_env()
144
+ or _DEFAULT_AUTOMATION_SPAN_NAME
145
+ )
146
+ context = automation_observability_context(
147
+ metadata=metadata,
148
+ tags=observability_tags,
149
+ span_name=resolved_span_name,
150
+ parent_span_context=parent_span_context,
151
+ )
152
+ if context_metadata := context.get("observability_metadata"):
153
+ kwargs["observability_metadata"] = context_metadata
154
+ if context_tags := context.get("observability_tags"):
155
+ kwargs["observability_tags"] = context_tags
156
+ if context_span_name := context.get("observability_span_name"):
157
+ kwargs["observability_span_name"] = context_span_name
158
+ if context_parent := context.get("observability_parent_span_context"):
159
+ kwargs["observability_parent_span_context"] = context_parent
160
+
161
+ if include_conversation_tags:
162
+ merged_conversation_tags = automation_conversation_tags(metadata)
163
+ if conversation_tags:
164
+ merged_conversation_tags.update(dict(conversation_tags))
165
+ if merged_conversation_tags:
166
+ kwargs["tags"] = merged_conversation_tags
167
+
168
+ return kwargs
169
+
170
+
171
+ def automation_observability_headers(
172
+ *,
173
+ metadata: Mapping[str, TraceMetadataValue] | None = None,
174
+ tags: Sequence[str] | None = None,
175
+ span_name: str | None = None,
176
+ parent_span_context: str | None = None,
177
+ ) -> dict[str, str]:
178
+ """Return observability headers for direct agent-server API calls."""
179
+ context = automation_observability_context(
180
+ metadata=metadata,
181
+ tags=tags,
182
+ span_name=span_name,
183
+ parent_span_context=parent_span_context,
184
+ )
185
+ headers: dict[str, str] = {}
186
+ if context_metadata := context.get("observability_metadata"):
187
+ headers["X-OpenHands-Observability-Metadata"] = json.dumps(
188
+ context_metadata, separators=(",", ":")
189
+ )
190
+ if context_tags := context.get("observability_tags"):
191
+ headers["X-OpenHands-Observability-Tags"] = ",".join(context_tags)
192
+ if context_span_name := context.get("observability_span_name"):
193
+ headers["X-OpenHands-Observability-Span-Name"] = context_span_name
194
+ if context_parent := context.get("observability_parent_span_context"):
195
+ headers["X-OpenHands-Observability-Parent-Span-Context"] = context_parent
196
+
197
+ try:
198
+ from opentelemetry.propagate import inject
199
+
200
+ inject(headers)
201
+ except Exception:
202
+ logger.debug(
203
+ "Failed to inject OpenTelemetry propagation headers", exc_info=True
204
+ )
205
+ return headers
206
+
207
+
208
+ __all__ = [
209
+ "AutomationConversationKwargs",
210
+ "AutomationObservabilityContext",
211
+ "automation_conversation_kwargs",
212
+ "automation_conversation_tags",
213
+ "automation_observability_context",
214
+ "automation_observability_headers",
215
+ ]
@@ -10,11 +10,16 @@ repo/skills/suffix/secrets/datetime are preset-independent -- so a planning agen
10
10
  with an ``agent_context`` still gets its dynamic block.
11
11
  """
12
12
 
13
+ from dataclasses import dataclass, field
13
14
  from enum import StrEnum
14
15
  from typing import Final
15
16
 
16
17
  from openhands.sdk.context.prompts.registry import PromptRegistry
17
- from openhands.sdk.context.prompts.section import PromptSection
18
+ from openhands.sdk.context.prompts.section import (
19
+ CacheTier,
20
+ PromptContext,
21
+ PromptSection,
22
+ )
18
23
  from openhands.sdk.context.prompts.sections.dynamic import (
19
24
  AvailableSkillsSection,
20
25
  CustomSecretsSection,
@@ -25,7 +30,6 @@ from openhands.sdk.context.prompts.sections.dynamic import (
25
30
  )
26
31
  from openhands.sdk.context.prompts.sections.planning import PlanningSection
27
32
  from openhands.sdk.context.prompts.sections.static import (
28
- BrowserSection,
29
33
  CodeQualitySection,
30
34
  EfficiencySection,
31
35
  EnvironmentSetupSection,
@@ -33,6 +37,7 @@ from openhands.sdk.context.prompts.sections.static import (
33
37
  FileSystemSection,
34
38
  MemorySection,
35
39
  ModelSpecificSection,
40
+ PersonaSection,
36
41
  ProblemSolvingSection,
37
42
  ProcessManagementSection,
38
43
  PullRequestsSection,
@@ -41,6 +46,7 @@ from openhands.sdk.context.prompts.sections.static import (
41
46
  SecuritySection,
42
47
  SelfDocumentationSection,
43
48
  SoulSection,
49
+ ToolGuidanceSection,
44
50
  TroubleshootingSection,
45
51
  VersionControlSection,
46
52
  )
@@ -56,28 +62,50 @@ class PromptPreset(StrEnum):
56
62
  PLANNING = "planning"
57
63
 
58
64
 
65
+ @dataclass(frozen=True, slots=True)
66
+ class _ReplacedByPersona:
67
+ """A persona-layer section: dropped when the agent supplies its own persona."""
68
+
69
+ section: PromptSection
70
+ name: str = field(init=False)
71
+ cache_tier: CacheTier = field(init=False)
72
+
73
+ def __post_init__(self) -> None:
74
+ object.__setattr__(self, "name", self.section.name)
75
+ object.__setattr__(self, "cache_tier", self.section.cache_tier)
76
+
77
+ def guard(self, ctx: PromptContext) -> bool:
78
+ return ctx.persona is None and self.section.guard(ctx)
79
+
80
+ def render(self, ctx: PromptContext) -> str | None:
81
+ return self.section.render(ctx)
82
+
83
+
59
84
  _DEFAULT_STATIC_SECTIONS: Final[tuple[PromptSection, ...]] = (
60
- SoulSection(),
61
- RoleSection(),
85
+ PersonaSection(), # guard: persona set
86
+ _ReplacedByPersona(SoulSection()),
87
+ _ReplacedByPersona(RoleSection()),
62
88
  MemorySection(),
63
- EfficiencySection(),
64
- FileSystemSection(),
65
- CodeQualitySection(),
66
- VersionControlSection(),
67
- PullRequestsSection(),
68
- ProblemSolvingSection(),
69
- SelfDocumentationSection(),
89
+ _ReplacedByPersona(EfficiencySection()),
90
+ _ReplacedByPersona(FileSystemSection()),
91
+ _ReplacedByPersona(CodeQualitySection()),
92
+ _ReplacedByPersona(VersionControlSection()),
93
+ _ReplacedByPersona(PullRequestsSection()),
94
+ _ReplacedByPersona(ProblemSolvingSection()),
95
+ _ReplacedByPersona(SelfDocumentationSection()),
70
96
  SecuritySection(), # guard: security_policy_filename set
71
97
  SecurityRiskAssessmentSection(), # guard: llm_security_analyzer
72
- BrowserSection(), # guard: ctx.enable_browser
98
+ ToolGuidanceSection(), # guard: loaded tools supply prompt_guidance
73
99
  ExternalServicesSection(),
74
- EnvironmentSetupSection(),
75
- TroubleshootingSection(),
100
+ _ReplacedByPersona(EnvironmentSetupSection()),
101
+ _ReplacedByPersona(TroubleshootingSection()),
76
102
  ProcessManagementSection(),
77
103
  ModelSpecificSection(), # guard: model_family resolved
78
104
  )
79
-
80
- _PLANNING_STATIC_SECTIONS: Final[tuple[PromptSection, ...]] = (PlanningSection(),)
105
+ _PLANNING_STATIC_SECTIONS: Final[tuple[PromptSection, ...]] = (
106
+ PersonaSection(),
107
+ PlanningSection(),
108
+ )
81
109
 
82
110
  _DYNAMIC_SECTIONS: Final[tuple[PromptSection, ...]] = (
83
111
  RepoContextSection(), # guard: gated repo skills present
@@ -53,8 +53,8 @@ class PromptContext(BaseModel):
53
53
  """Frozen snapshot of everything that shapes a system prompt.
54
54
 
55
55
  ``template_kwargs`` is the resolved kwarg dict for the static, cache-stable
56
- block; the other fields snapshot per-conversation signals. ``enable_browser``
57
- / ``model_family`` / ``cli_mode`` are typed views over ``template_kwargs``.
56
+ block; the other fields snapshot per-conversation signals. ``model_family`` /
57
+ ``cli_mode`` are typed views over ``template_kwargs``.
58
58
  """
59
59
 
60
60
  model_config = ConfigDict(frozen=True)
@@ -63,6 +63,7 @@ class PromptContext(BaseModel):
63
63
  default_factory=dict, validate_default=True
64
64
  )
65
65
  tool_names: tuple[str, ...] = Field(default_factory=tuple)
66
+ tool_guidance: tuple[str, ...] = Field(default_factory=tuple)
66
67
  platform: Platform = Field(default_factory=Platform.current)
67
68
  working_dir: str | None = None
68
69
  now: str | None = None
@@ -86,10 +87,6 @@ class PromptContext(BaseModel):
86
87
  # store a read-only copy so the snapshot and its views cannot drift.
87
88
  return MappingProxyType(dict(value))
88
89
 
89
- @property
90
- def enable_browser(self) -> bool:
91
- return bool(self.template_kwargs.get("enable_browser", False))
92
-
93
90
  @property
94
91
  def model_family(self) -> str | None:
95
92
  value = self.template_kwargs.get("model_family")
@@ -99,6 +96,11 @@ class PromptContext(BaseModel):
99
96
  def cli_mode(self) -> bool:
100
97
  return bool(self.template_kwargs.get("cli_mode", False))
101
98
 
99
+ @property
100
+ def persona(self) -> str | None:
101
+ value = self.template_kwargs.get("persona")
102
+ return value if isinstance(value, str) and value.strip() else None
103
+
102
104
 
103
105
  @runtime_checkable
104
106
  class PromptSection(Protocol):
@@ -109,17 +111,20 @@ class PromptSection(Protocol):
109
111
  :meth:`guard` decides whether the section applies (if ``False`` it is
110
112
  skipped and :meth:`render` never runs), then :meth:`render` produces the
111
113
  text. The split is intentional -- ``guard`` returning ``False`` means "not
112
- applicable here" (e.g. browser disabled, wrong platform), while ``render``
113
- returning ``None`` means "applicable, but nothing to add right now" (e.g. no
114
- skills present).
114
+ applicable here" (e.g. security policy disabled), while ``render`` returning
115
+ ``None`` means "applicable, but nothing to add right now" (e.g. no skills
116
+ present).
115
117
 
116
118
  Both must be pure -- read-only on ``ctx``, no I/O -- so sections are
117
119
  testable in isolation. ``name`` is the registry's unique key for
118
120
  dedup/override; ``cache_tier`` selects the static or dynamic block.
119
121
  """
120
122
 
121
- name: str
122
- cache_tier: CacheTier
123
+ @property
124
+ def name(self) -> str: ...
125
+
126
+ @property
127
+ def cache_tier(self) -> CacheTier: ...
123
128
 
124
129
  def guard(self, ctx: PromptContext) -> bool:
125
130
  """Return ``True`` if this section applies to ``ctx``."""
@@ -17,7 +17,6 @@ from openhands.sdk.context.prompts.sections.dynamic import (
17
17
  )
18
18
  from openhands.sdk.context.prompts.sections.planning import PlanningSection
19
19
  from openhands.sdk.context.prompts.sections.static import (
20
- BrowserSection,
21
20
  CodeQualitySection,
22
21
  EfficiencySection,
23
22
  EnvironmentSetupSection,
@@ -25,6 +24,7 @@ from openhands.sdk.context.prompts.sections.static import (
25
24
  FileSystemSection,
26
25
  MemorySection,
27
26
  ModelSpecificSection,
27
+ PersonaSection,
28
28
  ProblemSolvingSection,
29
29
  ProcessManagementSection,
30
30
  PullRequestsSection,
@@ -33,6 +33,7 @@ from openhands.sdk.context.prompts.sections.static import (
33
33
  SecuritySection,
34
34
  SelfDocumentationSection,
35
35
  SoulSection,
36
+ ToolGuidanceSection,
36
37
  TroubleshootingSection,
37
38
  VersionControlSection,
38
39
  )
@@ -40,7 +41,6 @@ from openhands.sdk.context.prompts.sections.static import (
40
41
 
41
42
  __all__ = [
42
43
  "AvailableSkillsSection",
43
- "BrowserSection",
44
44
  "CodeQualitySection",
45
45
  "CustomSecretsSection",
46
46
  "CustomSuffixSection",
@@ -52,6 +52,7 @@ __all__ = [
52
52
  "MemoryContextSection",
53
53
  "MemorySection",
54
54
  "ModelSpecificSection",
55
+ "PersonaSection",
55
56
  "PlanningSection",
56
57
  "ProblemSolvingSection",
57
58
  "ProcessManagementSection",
@@ -62,6 +63,7 @@ __all__ = [
62
63
  "SecuritySection",
63
64
  "SelfDocumentationSection",
64
65
  "SoulSection",
66
+ "ToolGuidanceSection",
65
67
  "TroubleshootingSection",
66
68
  "VersionControlSection",
67
69
  ]
@@ -23,7 +23,6 @@ from openhands.sdk.utils.path import get_user_persistence_dir, to_posix_path
23
23
 
24
24
 
25
25
  __all__ = [
26
- "BrowserSection",
27
26
  "CodeQualitySection",
28
27
  "EfficiencySection",
29
28
  "EnvironmentSetupSection",
@@ -31,6 +30,7 @@ __all__ = [
31
30
  "FileSystemSection",
32
31
  "MemorySection",
33
32
  "ModelSpecificSection",
33
+ "PersonaSection",
34
34
  "ProblemSolvingSection",
35
35
  "ProcessManagementSection",
36
36
  "PullRequestsSection",
@@ -39,6 +39,7 @@ __all__ = [
39
39
  "SecuritySection",
40
40
  "SelfDocumentationSection",
41
41
  "SoulSection",
42
+ "ToolGuidanceSection",
42
43
  "TroubleshootingSection",
43
44
  "VersionControlSection",
44
45
  ]
@@ -99,6 +100,18 @@ class RoleSection(_StaticTextSection):
99
100
  </ROLE>"""
100
101
 
101
102
 
103
+ class PersonaSection(_StaticTextSection):
104
+ """The agent's own persona, standing in for the persona sections it replaces."""
105
+
106
+ name = "persona"
107
+
108
+ def guard(self, ctx: PromptContext) -> bool:
109
+ return ctx.persona is not None
110
+
111
+ def render(self, ctx: PromptContext) -> str | None:
112
+ return ctx.persona
113
+
114
+
102
115
  class MemorySection(_StaticTextSection):
103
116
  """``<MEMORY>`` -- exactly one of two guidance variants fills the block:
104
117
  the default ``AGENTS.md`` guidance, or the two-tier persistent-memory
@@ -378,21 +391,16 @@ When an action originates from or is influenced by repository-provided context (
378
391
  return _refine(body, ctx.platform)
379
392
 
380
393
 
381
- class BrowserSection(_StaticTextSection):
382
- name = "browser"
383
- body = """\
384
- <BROWSER_TOOLS>
385
- You have a browser for navigating pages and interacting with web UIs.
386
- * Try curl/wget/fetch first. Use the browser only when simpler tools fail or the page requires JS/interaction.
387
- * ALWAYS call `browser_get_state` before EVERY `browser_click` or `browser_type` — indices change after each action. Flow: navigate → get_state → interact → get_state → get_content.
388
- * Max 10 browser actions per sub-task. If stuck, switch approach entirely.
389
- * If 20+ total steps without converging, stop exploring and commit to your best answer.
390
- * On 403/CAPTCHA/login wall: try one alternative, then abandon the browser.
391
- * Do NOT submit forms or create accounts unless explicitly asked.
392
- </BROWSER_TOOLS>"""
394
+ class ToolGuidanceSection(_StaticTextSection):
395
+ """Usage guidance supplied by the loaded tools (e.g. ``<BROWSER_TOOLS>``)."""
396
+
397
+ name = "tool_guidance"
393
398
 
394
399
  def guard(self, ctx: PromptContext) -> bool:
395
- return ctx.enable_browser
400
+ return bool(ctx.tool_guidance)
401
+
402
+ def render(self, ctx: PromptContext) -> str | None:
403
+ return "\n\n".join(ctx.tool_guidance)
396
404
 
397
405
 
398
406
  class ExternalServicesSection(_StaticTextSection):