openhands-sdk 1.7.2__tar.gz → 1.7.4__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 (185) hide show
  1. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/PKG-INFO +2 -2
  2. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/agent.py +26 -10
  3. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/base.py +53 -15
  4. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/condenser/__init__.py +2 -0
  5. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/condenser/base.py +59 -8
  6. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +42 -4
  7. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/skills/skill.py +336 -118
  8. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/view.py +2 -0
  9. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/impl/remote_conversation.py +110 -29
  10. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/state.py +9 -5
  11. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/llm.py +1 -2
  12. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/options/chat_options.py +4 -1
  13. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/utils/model_features.py +1 -0
  14. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/utils/verified_models.py +1 -1
  15. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/mcp/tool.py +3 -1
  16. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/tool/registry.py +23 -0
  17. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/tool/schema.py +6 -3
  18. openhands_sdk-1.7.4/openhands/sdk/utils/models.py +296 -0
  19. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/workspace/base.py +22 -0
  20. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/workspace/local.py +16 -0
  21. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands_sdk.egg-info/PKG-INFO +2 -2
  22. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands_sdk.egg-info/requires.txt +1 -1
  23. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/pyproject.toml +2 -2
  24. openhands_sdk-1.7.2/openhands/sdk/utils/models.py +0 -570
  25. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/__init__.py +0 -0
  26. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/__init__.py +0 -0
  27. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/in_context_learning_example.j2 +0 -0
  28. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +0 -0
  29. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +0 -0
  30. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +0 -0
  31. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +0 -0
  32. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +0 -0
  33. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/security_policy.j2 +0 -0
  34. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/security_risk_assessment.j2 +0 -0
  35. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/self_documentation.j2 +0 -0
  36. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/system_prompt.j2 +0 -0
  37. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/system_prompt_interactive.j2 +0 -0
  38. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +0 -0
  39. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/system_prompt_planning.j2 +0 -0
  40. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +0 -0
  41. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/agent/utils.py +0 -0
  42. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/__init__.py +0 -0
  43. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/agent_context.py +0 -0
  44. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  45. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  46. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
  47. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/condenser/utils.py +0 -0
  48. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/prompts/__init__.py +0 -0
  49. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/prompts/prompt.py +0 -0
  50. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  51. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  52. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/prompts/templates/system_message_suffix.j2 +0 -0
  53. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/skills/__init__.py +0 -0
  54. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/skills/exceptions.py +0 -0
  55. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/skills/trigger.py +0 -0
  56. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/context/skills/types.py +0 -0
  57. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/__init__.py +0 -0
  58. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/base.py +0 -0
  59. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/conversation.py +0 -0
  60. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/conversation_stats.py +0 -0
  61. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/event_store.py +0 -0
  62. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/events_list_base.py +0 -0
  63. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/exceptions.py +0 -0
  64. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/fifo_lock.py +0 -0
  65. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/impl/__init__.py +0 -0
  66. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/impl/local_conversation.py +0 -0
  67. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/persistence_const.py +0 -0
  68. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/response_utils.py +0 -0
  69. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/secret_registry.py +0 -0
  70. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/serialization_diff.py +0 -0
  71. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/stuck_detector.py +0 -0
  72. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/title_utils.py +0 -0
  73. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/types.py +0 -0
  74. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  75. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/visualizer/base.py +0 -0
  76. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/conversation/visualizer/default.py +0 -0
  77. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/critic/__init__.py +0 -0
  78. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/critic/base.py +0 -0
  79. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/critic/impl/__init__.py +0 -0
  80. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  81. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  82. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  83. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/__init__.py +0 -0
  84. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/base.py +0 -0
  85. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/condenser.py +0 -0
  86. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/conversation_error.py +0 -0
  87. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/conversation_state.py +0 -0
  88. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/llm_completion_log.py +0 -0
  89. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  90. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/llm_convertible/action.py +0 -0
  91. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/llm_convertible/message.py +0 -0
  92. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  93. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/llm_convertible/system.py +0 -0
  94. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/token.py +0 -0
  95. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/types.py +0 -0
  96. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/event/user_action.py +0 -0
  97. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/git/exceptions.py +0 -0
  98. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/git/git_changes.py +0 -0
  99. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/git/git_diff.py +0 -0
  100. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/git/models.py +0 -0
  101. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/git/utils.py +0 -0
  102. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/hooks/__init__.py +0 -0
  103. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/hooks/config.py +0 -0
  104. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  105. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/hooks/executor.py +0 -0
  106. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/hooks/manager.py +0 -0
  107. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/hooks/types.py +0 -0
  108. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/io/__init__.py +0 -0
  109. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/io/base.py +0 -0
  110. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/io/cache.py +0 -0
  111. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/io/local.py +0 -0
  112. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/io/memory.py +0 -0
  113. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/__init__.py +0 -0
  114. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/exceptions/__init__.py +0 -0
  115. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/exceptions/classifier.py +0 -0
  116. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/exceptions/mapping.py +0 -0
  117. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/exceptions/types.py +0 -0
  118. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/llm_registry.py +0 -0
  119. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/llm_response.py +0 -0
  120. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/message.py +0 -0
  121. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  122. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  123. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/options/__init__.py +0 -0
  124. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/options/common.py +0 -0
  125. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/options/responses_options.py +0 -0
  126. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/router/__init__.py +0 -0
  127. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/router/base.py +0 -0
  128. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  129. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/router/impl/random.py +0 -0
  130. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/streaming.py +0 -0
  131. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/utils/metrics.py +0 -0
  132. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/utils/model_info.py +0 -0
  133. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  134. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
  135. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/utils/telemetry.py +0 -0
  136. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  137. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/logger/__init__.py +0 -0
  138. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/logger/logger.py +0 -0
  139. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/logger/rolling.py +0 -0
  140. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/mcp/__init__.py +0 -0
  141. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/mcp/client.py +0 -0
  142. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/mcp/definition.py +0 -0
  143. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/mcp/exceptions.py +0 -0
  144. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/mcp/utils.py +0 -0
  145. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/observability/__init__.py +0 -0
  146. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/observability/laminar.py +0 -0
  147. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/observability/utils.py +0 -0
  148. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/py.typed +0 -0
  149. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/secret/__init__.py +0 -0
  150. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/secret/secrets.py +0 -0
  151. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/security/__init__.py +0 -0
  152. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/security/analyzer.py +0 -0
  153. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/security/confirmation_policy.py +0 -0
  154. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/security/llm_analyzer.py +0 -0
  155. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/security/risk.py +0 -0
  156. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/tool/__init__.py +0 -0
  157. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/tool/builtins/__init__.py +0 -0
  158. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/tool/builtins/finish.py +0 -0
  159. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/tool/builtins/think.py +0 -0
  160. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/tool/spec.py +0 -0
  161. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/tool/tool.py +0 -0
  162. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/__init__.py +0 -0
  163. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/async_executor.py +0 -0
  164. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/async_utils.py +0 -0
  165. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/cipher.py +0 -0
  166. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/command.py +0 -0
  167. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/deprecation.py +0 -0
  168. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/github.py +0 -0
  169. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/json.py +0 -0
  170. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/paging.py +0 -0
  171. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/pydantic_diff.py +0 -0
  172. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  173. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/truncate.py +0 -0
  174. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/utils/visualize.py +0 -0
  175. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/workspace/__init__.py +0 -0
  176. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/workspace/models.py +0 -0
  177. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/workspace/remote/__init__.py +0 -0
  178. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
  179. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/workspace/remote/base.py +0 -0
  180. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
  181. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands/sdk/workspace/workspace.py +0 -0
  182. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands_sdk.egg-info/SOURCES.txt +0 -0
  183. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  184. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/openhands_sdk.egg-info/top_level.txt +0 -0
  185. {openhands_sdk-1.7.2 → openhands_sdk-1.7.4}/setup.cfg +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.7.2
3
+ Version: 1.7.4
4
4
  Summary: OpenHands SDK - Core functionality for building AI agents
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: deprecation>=2.1.0
7
7
  Requires-Dist: fastmcp>=2.11.3
8
8
  Requires-Dist: httpx>=0.27.0
9
9
  Requires-Dist: litellm>=1.80.10
10
- Requires-Dist: pydantic>=2.11.7
10
+ Requires-Dist: pydantic>=2.12.5
11
11
  Requires-Dist: python-frontmatter>=1.1.0
12
12
  Requires-Dist: python-json-logger>=3.3.0
13
13
  Requires-Dist: tenacity>=9.1.2
@@ -27,7 +27,10 @@ from openhands.sdk.event import (
27
27
  TokenEvent,
28
28
  UserRejectObservation,
29
29
  )
30
- from openhands.sdk.event.condenser import Condensation, CondensationRequest
30
+ from openhands.sdk.event.condenser import (
31
+ Condensation,
32
+ CondensationRequest,
33
+ )
31
34
  from openhands.sdk.llm import (
32
35
  LLMResponse,
33
36
  Message,
@@ -502,16 +505,29 @@ class Agent(AgentBase):
502
505
  )
503
506
 
504
507
  # Execute actions!
505
- if should_enable_observability():
506
- tool_name = extract_action_name(action_event)
507
- observation: Observation = observe(name=tool_name, span_type="TOOL")(tool)(
508
- action_event.action, conversation
508
+ try:
509
+ if should_enable_observability():
510
+ tool_name = extract_action_name(action_event)
511
+ observation: Observation = observe(name=tool_name, span_type="TOOL")(
512
+ tool
513
+ )(action_event.action, conversation)
514
+ else:
515
+ observation = tool(action_event.action, conversation)
516
+ assert isinstance(observation, Observation), (
517
+ f"Tool '{tool.name}' executor must return an Observation"
509
518
  )
510
- else:
511
- observation = tool(action_event.action, conversation)
512
- assert isinstance(observation, Observation), (
513
- f"Tool '{tool.name}' executor must return an Observation"
514
- )
519
+ except ValueError as e:
520
+ # Tool execution raised a ValueError (e.g., invalid argument combination)
521
+ # Convert to AgentErrorEvent so the agent can correct itself
522
+ err = f"Error executing tool '{tool.name}': {e}"
523
+ logger.warning(err)
524
+ error_event = AgentErrorEvent(
525
+ error=err,
526
+ tool_name=tool.name,
527
+ tool_call_id=action_event.tool_call.id,
528
+ )
529
+ on_event(error_event)
530
+ return error_event
515
531
 
516
532
  obs_event = ObservationEvent(
517
533
  observation=observation,
@@ -2,7 +2,7 @@ import os
2
2
  import re
3
3
  import sys
4
4
  from abc import ABC, abstractmethod
5
- from collections.abc import Generator, Iterable
5
+ from collections.abc import Generator, Iterable, Sequence
6
6
  from concurrent.futures import ThreadPoolExecutor
7
7
  from typing import TYPE_CHECKING, Any
8
8
 
@@ -300,10 +300,19 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
300
300
  NOTE: state will be mutated in-place.
301
301
  """
302
302
 
303
- def resolve_diff_from_deserialized(self, persisted: "AgentBase") -> "AgentBase":
303
+ def resolve_diff_from_deserialized(
304
+ self,
305
+ persisted: "AgentBase",
306
+ events: "Sequence[Any] | None" = None,
307
+ ) -> "AgentBase":
304
308
  """
305
309
  Return a new AgentBase instance equivalent to `persisted` but with
306
310
  explicitly whitelisted fields (e.g. api_key) taken from `self`.
311
+
312
+ Args:
313
+ persisted: The persisted agent from the conversation state.
314
+ events: Optional event sequence to scan for used tools if tool
315
+ names don't match. Only scanned when needed (O(n) fallback).
307
316
  """
308
317
  if persisted.__class__ is not self.__class__:
309
318
  raise ValueError(
@@ -338,28 +347,57 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
338
347
  if self.agent_context is not None:
339
348
  updates["agent_context"] = self.agent_context
340
349
 
341
- # Create maps by tool name for easy lookup
342
- runtime_tools_map = {tool.name: tool for tool in self.tools}
343
- persisted_tools_map = {tool.name: tool for tool in persisted.tools}
344
-
345
- # Check that tool names match
346
- runtime_names = set(runtime_tools_map.keys())
347
- persisted_names = set(persisted_tools_map.keys())
350
+ # Get tool names for comparison
351
+ runtime_names = {tool.name for tool in self.tools}
352
+ persisted_names = {tool.name for tool in persisted.tools}
353
+
354
+ # If tool names match exactly, no need to check event history
355
+ if runtime_names == persisted_names:
356
+ # Tools unchanged, proceed normally
357
+ pass
358
+ elif events is not None:
359
+ # Tool names differ - scan events to find which tools were actually used
360
+ # This is O(n) but only happens when tools change
361
+ from openhands.sdk.event import ActionEvent
362
+
363
+ used_tools = {
364
+ event.tool_name
365
+ for event in events
366
+ if isinstance(event, ActionEvent) and event.tool_name
367
+ }
348
368
 
349
- if runtime_names != persisted_names:
369
+ # Only require tools that were actually used in history
370
+ missing_used_tools = used_tools - runtime_names
371
+ if missing_used_tools:
372
+ raise ValueError(
373
+ f"Cannot resume conversation: tools that were used in history "
374
+ f"are missing from runtime: {sorted(missing_used_tools)}. "
375
+ f"Available tools: {sorted(runtime_names)}"
376
+ )
377
+ # Update tools to match runtime (allows new tools to be added)
378
+ updates["tools"] = self.tools
379
+ else:
380
+ # No events provided - strict matching (legacy behavior)
350
381
  missing_in_runtime = persisted_names - runtime_names
351
382
  missing_in_persisted = runtime_names - persisted_names
352
383
  error_msg = "Tools don't match between runtime and persisted agents."
353
384
  if missing_in_runtime:
354
- error_msg += f" Missing in runtime: {missing_in_runtime}."
385
+ error_msg += f" Missing in runtime: {sorted(missing_in_runtime)}."
355
386
  if missing_in_persisted:
356
- error_msg += f" Missing in persisted: {missing_in_persisted}."
387
+ error_msg += f" Missing in persisted: {sorted(missing_in_persisted)}."
357
388
  raise ValueError(error_msg)
358
389
 
359
390
  reconciled = persisted.model_copy(update=updates)
360
- if self.model_dump(exclude_none=True) != reconciled.model_dump(
361
- exclude_none=True
362
- ):
391
+
392
+ # Validate agent equality - exclude tools from comparison since we
393
+ # already validated tool requirements above
394
+ exclude_fields = {"tools"} if events is not None else set()
395
+ self_dump = self.model_dump(exclude_none=True, exclude=exclude_fields)
396
+ reconciled_dump = reconciled.model_dump(
397
+ exclude_none=True, exclude=exclude_fields
398
+ )
399
+
400
+ if self_dump != reconciled_dump:
363
401
  raise ValueError(
364
402
  "The Agent provided is different from the one in persisted state.\n"
365
403
  f"Diff: {pretty_pydantic_diff(self, reconciled)}"
@@ -1,5 +1,6 @@
1
1
  from openhands.sdk.context.condenser.base import (
2
2
  CondenserBase,
3
+ NoCondensationAvailableException,
3
4
  RollingCondenser,
4
5
  )
5
6
  from openhands.sdk.context.condenser.llm_summarizing_condenser import (
@@ -15,4 +16,5 @@ __all__ = [
15
16
  "NoOpCondenser",
16
17
  "PipelineCondenser",
17
18
  "LLMSummarizingCondenser",
19
+ "NoCondensationAvailableException",
18
20
  ]
@@ -1,4 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
+ from enum import Enum
2
3
  from logging import getLogger
3
4
 
4
5
  from openhands.sdk.context.view import View
@@ -66,6 +67,29 @@ class PipelinableCondenserBase(CondenserBase):
66
67
  condenser should not nest another pipeline condenser)"""
67
68
 
68
69
 
70
+ class NoCondensationAvailableException(Exception):
71
+ """Raised when a condenser is asked to provide a condensation but none is available.
72
+
73
+ This can happen if the condenser's `should_condense` method returns True, but due to
74
+ API constraints no condensation can be generated.
75
+
76
+ When this exception is raised from a rolling condenser's `get_condensation` method,
77
+ the agent will fall back to using the uncondensed view for the next agent step.
78
+ """
79
+
80
+
81
+ class CondensationRequirement(Enum):
82
+ """The type of condensation required by a rolling condenser."""
83
+
84
+ HARD = "hard"
85
+ """Indicates that a condensation is required right now, and the agent cannot proceed
86
+ without it.
87
+ """
88
+
89
+ SOFT = "soft"
90
+ """Indicates that a condensation is desired but not strictly required."""
91
+
92
+
69
93
  class RollingCondenser(PipelinableCondenserBase, ABC):
70
94
  """Base class for a specialized condenser strategy that applies condensation to a
71
95
  rolling history.
@@ -73,15 +97,27 @@ class RollingCondenser(PipelinableCondenserBase, ABC):
73
97
  The rolling history is generated by `View.from_events`, which analyzes all events in
74
98
  the history and produces a `View` object representing what will be sent to the LLM.
75
99
 
76
- If `should_condense` says so, the condenser is then responsible for generating a
77
- `Condensation` object from the `View` object. This will be added to the event
78
- history which should -- when given to `get_view` -- produce the condensed `View` to
79
- be passed to the LLM.
100
+ If `condensation_requirement` says so, the condenser is then responsible for
101
+ generating a `Condensation` object from the `View` object. This will be added to the
102
+ event history which should -- when given to `get_view` -- produce the condensed
103
+ `View` to be passed to the LLM.
80
104
  """
81
105
 
82
106
  @abstractmethod
83
- def should_condense(self, view: View, agent_llm: LLM | None = None) -> bool:
84
- """Determine if a view should be condensed."""
107
+ def condensation_requirement(
108
+ self, view: View, agent_llm: LLM | None = None
109
+ ) -> CondensationRequirement | None:
110
+ """Determine how a view should be condensed.
111
+
112
+ Args:
113
+ view: The current view of the conversation history.
114
+ agent_llm: LLM instance used by the agent. Condensers use this for token
115
+ counting purposes. Defaults to None.
116
+
117
+ Returns:
118
+ CondensationRequirement | None: The type of condensation required, or None
119
+ if no condensation is needed.
120
+ """
85
121
 
86
122
  @abstractmethod
87
123
  def get_condensation(
@@ -92,8 +128,23 @@ class RollingCondenser(PipelinableCondenserBase, ABC):
92
128
  def condense(self, view: View, agent_llm: LLM | None = None) -> View | Condensation:
93
129
  # If we trigger the condenser-specific condensation threshold, compute and
94
130
  # return the condensation.
95
- if self.should_condense(view, agent_llm=agent_llm):
96
- return self.get_condensation(view, agent_llm=agent_llm)
131
+ request = self.condensation_requirement(view, agent_llm=agent_llm)
132
+ if request is not None:
133
+ try:
134
+ return self.get_condensation(view, agent_llm=agent_llm)
135
+
136
+ except NoCondensationAvailableException as e:
137
+ logger.debug(f"No condensation available: {e}")
138
+
139
+ if request == CondensationRequirement.SOFT:
140
+ # For soft requests, we can just return the uncondensed view. This
141
+ # request will _eventually_ be handled, but it's not critical that
142
+ # we do so immediately.
143
+ return view
144
+
145
+ # Otherwise re-raise the exception.
146
+ else:
147
+ raise e
97
148
 
98
149
  # Otherwise we're safe to just return the view.
99
150
  else:
@@ -4,7 +4,11 @@ from enum import Enum
4
4
 
5
5
  from pydantic import Field, model_validator
6
6
 
7
- from openhands.sdk.context.condenser.base import RollingCondenser
7
+ from openhands.sdk.context.condenser.base import (
8
+ CondensationRequirement,
9
+ NoCondensationAvailableException,
10
+ RollingCondenser,
11
+ )
8
12
  from openhands.sdk.context.condenser.utils import (
9
13
  get_suffix_length_for_token_reduction,
10
14
  get_total_token_count,
@@ -84,9 +88,30 @@ class LLMSummarizingCondenser(RollingCondenser):
84
88
 
85
89
  return reasons
86
90
 
87
- def should_condense(self, view: View, agent_llm: LLM | None = None) -> bool:
91
+ def condensation_requirement(
92
+ self, view: View, agent_llm: LLM | None = None
93
+ ) -> CondensationRequirement | None:
88
94
  reasons = self.get_condensation_reasons(view, agent_llm)
89
- return reasons != set()
95
+
96
+ # No reasons => no condensation needed.
97
+ if reasons == set():
98
+ return None
99
+
100
+ # If the reasons are for resource constraints, we can treat it as a soft
101
+ # requirement. We want to condense when we can, but there's still space in the
102
+ # context window or we'd also see Reason.REQUEST. That means we can delay the
103
+ # condensation if there isn't one available (based on the view's manipulation
104
+ # indices).
105
+ resource_reasons = {Reason.TOKENS, Reason.EVENTS}
106
+ if reasons.issubset(resource_reasons):
107
+ return CondensationRequirement.SOFT
108
+
109
+ # Requests -- whether they come from the user or the agent -- are always hard
110
+ # requirements. We need to condense now because:
111
+ # 1. the user expects it
112
+ # 2. the agent has no more room in the context window and can't continue
113
+ if Reason.REQUEST in reasons:
114
+ return CondensationRequirement.HARD
90
115
 
91
116
  def _get_summary_event_content(self, view: View) -> str:
92
117
  """Extract the text content from the summary event in the view, if any.
@@ -120,7 +145,12 @@ class LLMSummarizingCondenser(RollingCondenser):
120
145
 
121
146
  Returns:
122
147
  Condensation: The generated condensation object.
148
+
149
+ Raises:
150
+ ValueError: If forgotten_events is empty (0 events to condense).
123
151
  """
152
+ assert len(forgotten_events) > 0, "No events to condense."
153
+
124
154
  # Convert events to strings for the template
125
155
  event_strings = [str(forgotten_event) for forgotten_event in forgotten_events]
126
156
 
@@ -226,11 +256,19 @@ class LLMSummarizingCondenser(RollingCondenser):
226
256
  ) -> Condensation:
227
257
  # The condensation is dependent on the events we want to drop and the previous
228
258
  # summary.
229
- summary_event_content = self._get_summary_event_content(view)
230
259
  forgotten_events, summary_offset = self._get_forgotten_events(
231
260
  view, agent_llm=agent_llm
232
261
  )
233
262
 
263
+ if not forgotten_events:
264
+ raise NoCondensationAvailableException(
265
+ "Cannot condense 0 events. This typically occurs when a tool loop "
266
+ "spans almost the entire view, leaving no valid range for forgetting "
267
+ "events. Consider adjusting keep_first or max_size parameters."
268
+ )
269
+
270
+ summary_event_content = self._get_summary_event_content(view)
271
+
234
272
  return self._generate_condensation(
235
273
  summary_event_content=summary_event_content,
236
274
  forgotten_events=forgotten_events,