openhands-sdk 1.9.1__tar.gz → 1.11.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 (201) hide show
  1. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/PKG-INFO +1 -1
  2. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/agent.py +90 -16
  3. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/base.py +33 -46
  4. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/condenser/base.py +36 -3
  5. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +65 -24
  6. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +1 -5
  7. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/prompts/templates/system_message_suffix.j2 +2 -1
  8. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/skills/skill.py +2 -25
  9. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/view.py +108 -122
  10. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/__init__.py +2 -0
  11. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/conversation.py +18 -3
  12. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/exceptions.py +18 -0
  13. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/impl/local_conversation.py +211 -36
  14. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/impl/remote_conversation.py +151 -12
  15. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/stuck_detector.py +18 -9
  16. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/impl/api/critic.py +10 -7
  17. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/condenser.py +52 -2
  18. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/git/cached_repo.py +19 -0
  19. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/hooks/__init__.py +2 -0
  20. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/hooks/config.py +44 -4
  21. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/hooks/executor.py +2 -1
  22. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/__init__.py +16 -0
  23. openhands_sdk-1.11.0/openhands/sdk/llm/auth/__init__.py +28 -0
  24. openhands_sdk-1.11.0/openhands/sdk/llm/auth/credentials.py +157 -0
  25. openhands_sdk-1.11.0/openhands/sdk/llm/auth/openai.py +762 -0
  26. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/llm.py +222 -33
  27. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/message.py +65 -27
  28. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/options/chat_options.py +2 -1
  29. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/options/responses_options.py +8 -7
  30. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/utils/model_features.py +2 -0
  31. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/mcp/client.py +53 -6
  32. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/mcp/tool.py +24 -21
  33. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/mcp/utils.py +31 -23
  34. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/plugin/__init__.py +12 -1
  35. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/plugin/fetch.py +118 -14
  36. openhands_sdk-1.11.0/openhands/sdk/plugin/loader.py +111 -0
  37. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/plugin/plugin.py +155 -13
  38. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/plugin/types.py +163 -1
  39. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/secret/secrets.py +13 -1
  40. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/__init__.py +2 -0
  41. openhands_sdk-1.11.0/openhands/sdk/utils/async_utils.py +74 -0
  42. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/command.py +28 -1
  43. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/workspace/remote/base.py +8 -3
  44. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +40 -7
  45. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands_sdk.egg-info/PKG-INFO +1 -1
  46. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands_sdk.egg-info/SOURCES.txt +8 -0
  47. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/pyproject.toml +1 -1
  48. openhands_sdk-1.9.1/openhands/sdk/utils/async_utils.py +0 -39
  49. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/__init__.py +0 -0
  50. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/__init__.py +0 -0
  51. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/in_context_learning_example.j2 +0 -0
  52. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +0 -0
  53. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +0 -0
  54. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +0 -0
  55. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +0 -0
  56. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +0 -0
  57. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/security_policy.j2 +0 -0
  58. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/security_risk_assessment.j2 +0 -0
  59. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/self_documentation.j2 +0 -0
  60. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/system_prompt.j2 +0 -0
  61. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/system_prompt_interactive.j2 +0 -0
  62. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +0 -0
  63. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/system_prompt_planning.j2 +0 -0
  64. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +0 -0
  65. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/agent/utils.py +0 -0
  66. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/__init__.py +0 -0
  67. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/agent_context.py +0 -0
  68. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/condenser/__init__.py +0 -0
  69. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  70. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  71. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/condenser/utils.py +0 -0
  72. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/prompts/__init__.py +0 -0
  73. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/prompts/prompt.py +0 -0
  74. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  75. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  76. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/skills/__init__.py +0 -0
  77. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/skills/exceptions.py +0 -0
  78. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/skills/trigger.py +0 -0
  79. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/skills/types.py +0 -0
  80. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/context/skills/utils.py +0 -0
  81. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/base.py +0 -0
  82. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
  83. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/event_store.py +0 -0
  84. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/events_list_base.py +0 -0
  85. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
  86. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
  87. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/persistence_const.py +0 -0
  88. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/response_utils.py +0 -0
  89. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/secret_registry.py +0 -0
  90. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/serialization_diff.py +0 -0
  91. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/state.py +0 -0
  92. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/title_utils.py +0 -0
  93. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/types.py +0 -0
  94. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  95. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
  96. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
  97. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/__init__.py +0 -0
  98. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/base.py +0 -0
  99. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/impl/__init__.py +0 -0
  100. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  101. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  102. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  103. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/impl/api/client.py +0 -0
  104. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  105. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  106. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  107. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/critic/result.py +0 -0
  108. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/__init__.py +0 -0
  109. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/base.py +0 -0
  110. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/conversation_error.py +0 -0
  111. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/conversation_state.py +0 -0
  112. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/llm_completion_log.py +0 -0
  113. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  114. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
  115. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
  116. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  117. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
  118. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/token.py +0 -0
  119. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/types.py +0 -0
  120. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/event/user_action.py +0 -0
  121. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/git/exceptions.py +0 -0
  122. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/git/git_changes.py +0 -0
  123. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/git/git_diff.py +0 -0
  124. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/git/models.py +0 -0
  125. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/git/utils.py +0 -0
  126. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  127. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/hooks/manager.py +0 -0
  128. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/hooks/types.py +0 -0
  129. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/io/__init__.py +0 -0
  130. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/io/base.py +0 -0
  131. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/io/cache.py +0 -0
  132. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/io/local.py +0 -0
  133. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/io/memory.py +0 -0
  134. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
  135. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/exceptions/classifier.py +0 -0
  136. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
  137. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/exceptions/types.py +0 -0
  138. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/llm_registry.py +0 -0
  139. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/llm_response.py +0 -0
  140. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  141. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  142. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/options/__init__.py +0 -0
  143. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/options/common.py +0 -0
  144. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/router/__init__.py +0 -0
  145. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/router/base.py +0 -0
  146. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  147. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/router/impl/random.py +0 -0
  148. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/streaming.py +0 -0
  149. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/utils/metrics.py +0 -0
  150. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/utils/model_info.py +0 -0
  151. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  152. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
  153. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
  154. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  155. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
  156. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/logger/__init__.py +0 -0
  157. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/logger/logger.py +0 -0
  158. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/logger/rolling.py +0 -0
  159. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/mcp/__init__.py +0 -0
  160. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/mcp/definition.py +0 -0
  161. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/mcp/exceptions.py +0 -0
  162. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/observability/__init__.py +0 -0
  163. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/observability/laminar.py +0 -0
  164. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/observability/utils.py +0 -0
  165. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/py.typed +0 -0
  166. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/secret/__init__.py +0 -0
  167. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/security/__init__.py +0 -0
  168. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/security/analyzer.py +0 -0
  169. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/security/confirmation_policy.py +0 -0
  170. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/security/llm_analyzer.py +0 -0
  171. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/security/risk.py +0 -0
  172. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/tool/__init__.py +0 -0
  173. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
  174. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/tool/builtins/finish.py +0 -0
  175. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/tool/builtins/think.py +0 -0
  176. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/tool/registry.py +0 -0
  177. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/tool/schema.py +0 -0
  178. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/tool/spec.py +0 -0
  179. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/tool/tool.py +0 -0
  180. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/async_executor.py +0 -0
  181. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/cipher.py +0 -0
  182. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/deprecation.py +0 -0
  183. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/github.py +0 -0
  184. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/json.py +0 -0
  185. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/models.py +0 -0
  186. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/paging.py +0 -0
  187. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/pydantic_diff.py +0 -0
  188. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  189. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/truncate.py +0 -0
  190. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/utils/visualize.py +0 -0
  191. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/workspace/__init__.py +0 -0
  192. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/workspace/base.py +0 -0
  193. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/workspace/local.py +0 -0
  194. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/workspace/models.py +0 -0
  195. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
  196. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
  197. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands/sdk/workspace/workspace.py +0 -0
  198. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  199. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands_sdk.egg-info/requires.txt +0 -0
  200. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/openhands_sdk.egg-info/top_level.txt +0 -0
  201. {openhands_sdk-1.9.1 → openhands_sdk-1.11.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.9.1
3
+ Version: 1.11.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
@@ -67,6 +67,10 @@ from openhands.sdk.tool.builtins import (
67
67
  logger = get_logger(__name__)
68
68
  maybe_init_laminar()
69
69
 
70
+ # Maximum number of events to scan during init_state defensive checks.
71
+ # SystemPromptEvent must appear within this prefix (at index 0 or 1).
72
+ INIT_STATE_PREFIX_SCAN_WINDOW = 3
73
+
70
74
 
71
75
  class Agent(AgentBase):
72
76
  """Main agent implementation for OpenHands.
@@ -102,24 +106,94 @@ class Agent(AgentBase):
102
106
  state: ConversationState,
103
107
  on_event: ConversationCallbackType,
104
108
  ) -> None:
109
+ """Initialize conversation state.
110
+
111
+ Invariants enforced by this method:
112
+ - If a SystemPromptEvent is already present, it must be within the first 3
113
+ events (index 0 or 1 in practice; index 2 is included in the scan window
114
+ to detect a user message appearing before the system prompt).
115
+ - A user MessageEvent should not appear before the SystemPromptEvent.
116
+
117
+ These invariants keep event ordering predictable for downstream components
118
+ (condenser, UI, etc.) and also prevent accidentally materializing the full
119
+ event history during initialization.
120
+ """
105
121
  super().init_state(state, on_event=on_event)
106
- # TODO(openhands): we should add test to test this init_state will actually
107
- # modify state in-place
108
-
109
- llm_convertible_messages = [
110
- event for event in state.events if isinstance(event, LLMConvertibleEvent)
111
- ]
112
- if len(llm_convertible_messages) == 0:
113
- # Prepare system message
114
- event = SystemPromptEvent(
115
- source="agent",
116
- system_prompt=TextContent(text=self.system_message),
117
- # Tools are stored as ToolDefinition objects and converted to
118
- # OpenAI format with security_risk parameter during LLM completion.
119
- # See make_llm_completion() in agent/utils.py for details.
120
- tools=list(self.tools_map.values()),
122
+
123
+ # Defensive check: Analyze state to detect unexpected initialization scenarios
124
+ # These checks help diagnose issues related to lazy loading and event ordering
125
+ # See: https://github.com/OpenHands/software-agent-sdk/issues/1785
126
+ #
127
+ # NOTE: len() is O(1) for EventLog (file-backed implementation).
128
+ event_count = len(state.events)
129
+
130
+ # NOTE: state.events is intentionally an EventsListBase (Sequence-like), not
131
+ # a plain list. Avoid materializing the full history via list(state.events)
132
+ # here (conversations can reach 30k+ events).
133
+ #
134
+ # Invariant: when init_state is called, SystemPromptEvent (if present) must be
135
+ # at index 0 or 1.
136
+ #
137
+ # Rationale:
138
+ # - Local conversations start empty and init_state is responsible for adding
139
+ # the SystemPromptEvent as the first event.
140
+ # - Remote conversations may receive an initial ConversationStateUpdateEvent
141
+ # from the agent-server immediately after subscription. In a typical remote
142
+ # session prefix you may see:
143
+ # [ConversationStateUpdateEvent, SystemPromptEvent, MessageEvent, ...]
144
+ #
145
+ # We intentionally only inspect the first few events (cheap for both local and
146
+ # remote) to enforce this invariant.
147
+ prefix_events = state.events[:INIT_STATE_PREFIX_SCAN_WINDOW]
148
+
149
+ has_system_prompt = any(isinstance(e, SystemPromptEvent) for e in prefix_events)
150
+ has_user_message = any(
151
+ isinstance(e, MessageEvent) and e.source == "user" for e in prefix_events
152
+ )
153
+ # Log state for debugging initialization order issues
154
+ logger.debug(
155
+ f"init_state called: conversation_id={state.id}, "
156
+ f"event_count={event_count}, "
157
+ f"has_system_prompt={has_system_prompt}, "
158
+ f"has_user_message={has_user_message}"
159
+ )
160
+
161
+ if has_system_prompt:
162
+ # Restoring/resuming conversations is normal: a system prompt already
163
+ # present means this conversation was initialized previously.
164
+ logger.debug(
165
+ "init_state: SystemPromptEvent already present; skipping init. "
166
+ f"conversation_id={state.id}, event_count={event_count}."
121
167
  )
122
- on_event(event)
168
+ return
169
+
170
+ # Assert: A user message should never appear before the system prompt.
171
+ #
172
+ # NOTE: This is a best-effort check based on the first few events only.
173
+ # Remote conversations can include a ConversationStateUpdateEvent near the
174
+ # start, so we scan a small prefix window.
175
+ if has_user_message:
176
+ event_types = [type(e).__name__ for e in prefix_events]
177
+ logger.error(
178
+ f"init_state: User message found in prefix before SystemPromptEvent! "
179
+ f"conversation_id={state.id}, prefix_events={event_types}"
180
+ )
181
+ raise AssertionError(
182
+ "Unexpected state: user message exists before SystemPromptEvent. "
183
+ f"conversation_id={state.id}, event_count={event_count}, "
184
+ f"prefix_event_types={event_types}."
185
+ )
186
+
187
+ # Prepare system message
188
+ event = SystemPromptEvent(
189
+ source="agent",
190
+ system_prompt=TextContent(text=self.system_message),
191
+ # Tools are stored as ToolDefinition objects and converted to
192
+ # OpenAI format with security_risk parameter during LLM completion.
193
+ # See make_llm_completion() in agent/utils.py for details.
194
+ tools=list(self.tools_map.values()),
195
+ )
196
+ on_event(event)
123
197
 
124
198
  def _should_evaluate_with_critic(self, action: Action | None) -> bool:
125
199
  """Determine if critic should evaluate based on action type and mode."""
@@ -345,28 +345,28 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
345
345
  def verify(
346
346
  self,
347
347
  persisted: AgentBase,
348
- events: Sequence[Any] | None = None,
348
+ events: Sequence[Any] | None = None, # noqa: ARG002
349
349
  ) -> AgentBase:
350
350
  """Verify that we can resume this agent from persisted state.
351
351
 
352
- This PR's goal is to *not* reconcile configuration between persisted and
353
- runtime Agent instances. Instead, we verify compatibility requirements
354
- and then continue with the runtime-provided Agent.
352
+ We do not merge configuration between persisted and runtime Agent
353
+ instances. Instead, we verify compatibility requirements and then
354
+ continue with the runtime-provided Agent.
355
355
 
356
356
  Compatibility requirements:
357
357
  - Agent class/type must match.
358
- - Tools:
359
- - If events are provided, only tools that were actually used in history
360
- must exist in runtime.
361
- - If events are not provided, tool names must match exactly.
358
+ - Tools must match exactly (same tool names).
362
359
 
363
- All other configuration (LLM, agent_context, condenser, system prompts,
364
- etc.) can be freely changed between sessions.
360
+ Tools are part of the system prompt and cannot be changed mid-conversation.
361
+ To use different tools, start a new conversation or use conversation forking
362
+ (see https://github.com/OpenHands/OpenHands/issues/8560).
363
+
364
+ All other configuration (LLM, agent_context, condenser, etc.) can be
365
+ freely changed between sessions.
365
366
 
366
367
  Args:
367
368
  persisted: The agent loaded from persisted state.
368
- events: Optional event sequence to scan for used tools if tool names
369
- don't match.
369
+ events: Unused, kept for API compatibility.
370
370
 
371
371
  Returns:
372
372
  This runtime agent (self) if verification passes.
@@ -381,52 +381,39 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
381
381
  f"{self.__class__.__name__}."
382
382
  )
383
383
 
384
+ # Collect explicit tool names
384
385
  runtime_names = {tool.name for tool in self.tools}
385
386
  persisted_names = {tool.name for tool in persisted.tools}
386
387
 
387
- if runtime_names == persisted_names:
388
- return self
389
-
390
- if events is not None:
391
- from openhands.sdk.event import ActionEvent
392
-
393
- used_tools = {
394
- event.tool_name
395
- for event in events
396
- if isinstance(event, ActionEvent) and event.tool_name
397
- }
388
+ # Add builtin tool names from include_default_tools
389
+ # These are runtime names like 'finish', 'think'
390
+ for tool_class_name in self.include_default_tools:
391
+ tool_class = BUILT_IN_TOOL_CLASSES.get(tool_class_name)
392
+ if tool_class is not None:
393
+ runtime_names.add(tool_class.name)
398
394
 
399
- # Add builtin tool names from include_default_tools
400
- # These are runtime names like 'finish', 'think'
401
- for tool_class_name in self.include_default_tools:
402
- tool_class = BUILT_IN_TOOL_CLASSES.get(tool_class_name)
403
- if tool_class is not None:
404
- runtime_names.add(tool_class.name)
405
-
406
- # Only require tools that were actually used in history.
407
- missing_used_tools = used_tools - runtime_names
408
- if missing_used_tools:
409
- raise ValueError(
410
- "Cannot resume conversation: tools that were used in history "
411
- f"are missing from runtime: {sorted(missing_used_tools)}. "
412
- f"Available tools: {sorted(runtime_names)}"
413
- )
395
+ for tool_class_name in persisted.include_default_tools:
396
+ tool_class = BUILT_IN_TOOL_CLASSES.get(tool_class_name)
397
+ if tool_class is not None:
398
+ persisted_names.add(tool_class.name)
414
399
 
400
+ if runtime_names == persisted_names:
415
401
  return self
416
402
 
417
- # No events provided: strict tool name matching.
403
+ # Tools don't match - this is not allowed
418
404
  missing_in_runtime = persisted_names - runtime_names
419
- missing_in_persisted = runtime_names - persisted_names
405
+ added_in_runtime = runtime_names - persisted_names
420
406
 
421
407
  details: list[str] = []
422
408
  if missing_in_runtime:
423
- details.append(f"Missing in runtime: {sorted(missing_in_runtime)}")
424
- if missing_in_persisted:
425
- details.append(f"Missing in persisted: {sorted(missing_in_persisted)}")
409
+ details.append(f"removed: {sorted(missing_in_runtime)}")
410
+ if added_in_runtime:
411
+ details.append(f"added: {sorted(added_in_runtime)}")
426
412
 
427
- suffix = f" ({'; '.join(details)})" if details else ""
428
413
  raise ValueError(
429
- "Tools don't match between runtime and persisted agents." + suffix
414
+ f"Cannot resume conversation: tools cannot be changed mid-conversation "
415
+ f"({'; '.join(details)}). "
416
+ f"To use different tools, start a new conversation."
430
417
  )
431
418
 
432
419
  def model_dump_succint(self, **kwargs):
@@ -516,5 +503,5 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
516
503
  RuntimeError: If the agent has not been initialized.
517
504
  """
518
505
  if not self._initialized:
519
- raise RuntimeError("Agent not initialized; call initialize() before use")
506
+ raise RuntimeError("Agent not initialized; call _initialize() before use")
520
507
  return self._tools
@@ -103,6 +103,23 @@ class RollingCondenser(PipelinableCondenserBase, ABC):
103
103
  `View` to be passed to the LLM.
104
104
  """
105
105
 
106
+ def hard_context_reset(
107
+ self,
108
+ view: View, # noqa: ARG002
109
+ agent_llm: LLM | None = None, # noqa: ARG002
110
+ ) -> Condensation | None:
111
+ """Perform a hard context reset, if supported by the condenser.
112
+
113
+ By default, rolling condensers do not support hard context resets. Override this
114
+ method to implement hard context reset logic by returning a `Condensation`
115
+ object.
116
+
117
+ This method is invoked when:
118
+ - A HARD condensation requirement is triggered (e.g., by user request)
119
+ - But the condenser raises a NoCondensationAvailableException error
120
+ """
121
+ return None
122
+
106
123
  @abstractmethod
107
124
  def condensation_requirement(
108
125
  self, view: View, agent_llm: LLM | None = None
@@ -142,9 +159,25 @@ class RollingCondenser(PipelinableCondenserBase, ABC):
142
159
  # we do so immediately.
143
160
  return view
144
161
 
145
- # Otherwise re-raise the exception.
146
- else:
147
- raise e
162
+ elif request == CondensationRequirement.HARD:
163
+ # The agent has found itself in a situation where it cannot proceed
164
+ # without condensation, but the condenser cannot provide one. We'll
165
+ # try to recover from this situation by performing a hard context
166
+ # reset, if supported by the condenser.
167
+ try:
168
+ hard_reset_condensation = self.hard_context_reset(
169
+ view, agent_llm=agent_llm
170
+ )
171
+ if hard_reset_condensation is not None:
172
+ return hard_reset_condensation
173
+
174
+ # And if something goes wrong with the hard reset make sure we keep
175
+ # both errors in the stack
176
+ except Exception as hard_reset_exception:
177
+ raise hard_reset_exception from e
178
+
179
+ # In all other situations re-raise the exception.
180
+ raise e
148
181
 
149
182
  # Otherwise we're safe to just return the view.
150
183
  else:
@@ -17,9 +17,13 @@ from openhands.sdk.context.prompts import render_template
17
17
  from openhands.sdk.context.view import View
18
18
  from openhands.sdk.event.base import LLMConvertibleEvent
19
19
  from openhands.sdk.event.condenser import Condensation
20
- from openhands.sdk.event.llm_convertible import MessageEvent
21
20
  from openhands.sdk.llm import LLM, Message, TextContent
21
+ from openhands.sdk.logger import get_logger
22
22
  from openhands.sdk.observability.laminar import observe
23
+ from openhands.sdk.utils import maybe_truncate
24
+
25
+
26
+ logger = get_logger(__name__)
23
27
 
24
28
 
25
29
  class Reason(Enum):
@@ -48,6 +52,14 @@ class LLMSummarizingCondenser(RollingCondenser):
48
52
  `keep_first` events in the conversation will never be condensed or summarized.
49
53
  """
50
54
 
55
+ hard_context_reset_max_retries: int = Field(default=5, gt=0)
56
+ """Number of attempts to perform hard context reset before raising an error."""
57
+
58
+ hard_context_reset_context_scaling: float = Field(default=0.8, gt=0.0, lt=1.0)
59
+ """When performing hard context reset, if the summarization fails, reduce the max
60
+ size of each event string by this factor and retry.
61
+ """
62
+
51
63
  @model_validator(mode="after")
52
64
  def validate_keep_first_vs_max_size(self):
53
65
  events_from_tail = self.max_size // 2 - self.keep_first - 1
@@ -117,35 +129,20 @@ class LLMSummarizingCondenser(RollingCondenser):
117
129
  if Reason.REQUEST in reasons:
118
130
  return CondensationRequirement.HARD
119
131
 
120
- def _get_summary_event_content(self, view: View) -> str:
121
- """Extract the text content from the summary event in the view, if any.
122
-
123
- If there is no summary event or it does not contain text content, returns an
124
- empty string.
125
- """
126
- summary_event_content: str = ""
127
-
128
- summary_event = view.summary_event
129
- if isinstance(summary_event, MessageEvent):
130
- message_content = summary_event.llm_message.content[0]
131
- if isinstance(message_content, TextContent):
132
- summary_event_content = message_content.text
133
-
134
- return summary_event_content
135
-
136
132
  def _generate_condensation(
137
133
  self,
138
- summary_event_content: str,
139
134
  forgotten_events: Sequence[LLMConvertibleEvent],
140
135
  summary_offset: int,
136
+ max_event_str_length: int | None = None,
141
137
  ) -> Condensation:
142
138
  """Generate a condensation by using the condenser's LLM to summarize forgotten
143
139
  events.
144
140
 
145
141
  Args:
146
- summary_event_content: The content of the previous summary event.
147
142
  forgotten_events: The list of events to be summarized.
148
143
  summary_offset: The index where the summary event should be inserted.
144
+ max_event_str_length: Optional maximum length for each event string. If
145
+ provided, event strings longer than this will be truncated.
149
146
 
150
147
  Returns:
151
148
  Condensation: The generated condensation object.
@@ -156,12 +153,14 @@ class LLMSummarizingCondenser(RollingCondenser):
156
153
  assert len(forgotten_events) > 0, "No events to condense."
157
154
 
158
155
  # Convert events to strings for the template
159
- event_strings = [str(forgotten_event) for forgotten_event in forgotten_events]
156
+ event_strings = [
157
+ maybe_truncate(str(forgotten_event), truncate_after=max_event_str_length)
158
+ for forgotten_event in forgotten_events
159
+ ]
160
160
 
161
161
  prompt = render_template(
162
162
  os.path.join(os.path.dirname(__file__), "prompts"),
163
163
  "summarizing_prompt.j2",
164
- previous_summary=summary_event_content,
165
164
  events=event_strings,
166
165
  )
167
166
 
@@ -252,6 +251,51 @@ class LLMSummarizingCondenser(RollingCondenser):
252
251
  # Summary offset is the same as forgetting_start
253
252
  return forgotten_events, forgetting_start
254
253
 
254
+ @observe(ignore_inputs=["view", "agent_llm"])
255
+ def hard_context_reset(
256
+ self,
257
+ view: View,
258
+ agent_llm: LLM | None = None, # noqa: ARG002
259
+ ) -> Condensation | None:
260
+ """Perform a hard context reset by summarizing all events in the view.
261
+
262
+ Depending on how the hard context reset is triggered, this may fail (e.g., if
263
+ the view is too large for the summarizing LLM to handle). In that case, we keep
264
+ trimming down the contents until a summary can be generated.
265
+ """
266
+ max_event_str_length: int | None = None
267
+ attempts_remaining: int = self.hard_context_reset_max_retries
268
+
269
+ while attempts_remaining > 0:
270
+ try:
271
+ return self._generate_condensation(
272
+ forgotten_events=view.events,
273
+ summary_offset=0,
274
+ max_event_str_length=max_event_str_length,
275
+ )
276
+ except Exception as e:
277
+ # If we haven't set a max_event_str_length yet, set it as the largest
278
+ # event string length.
279
+ if max_event_str_length is None:
280
+ max_event_str_length = max(len(str(event)) for event in view.events)
281
+
282
+ # Since the summarization failed, reduce the max_event_str_length by 20%
283
+ assert max_event_str_length is not None
284
+ max_event_str_length = int(
285
+ max_event_str_length * self.hard_context_reset_context_scaling
286
+ )
287
+
288
+ # Log the exception so we can track these failures
289
+ logger.warning(
290
+ f"Hard context reset summarization failed with exception: {e}. "
291
+ f"Reducing max event size to {max_event_str_length} and retrying."
292
+ )
293
+
294
+ attempts_remaining -= 1
295
+
296
+ logger.error("Hard context reset summarization failed after multiple attempts.")
297
+ return None
298
+
255
299
  @observe(ignore_inputs=["view", "agent_llm"])
256
300
  def get_condensation(
257
301
  self, view: View, agent_llm: LLM | None = None
@@ -269,10 +313,7 @@ class LLMSummarizingCondenser(RollingCondenser):
269
313
  "events. Consider adjusting keep_first or max_size parameters."
270
314
  )
271
315
 
272
- summary_event_content = self._get_summary_event_content(view)
273
-
274
316
  return self._generate_condensation(
275
- summary_event_content=summary_event_content,
276
317
  forgotten_events=forgotten_events,
277
318
  summary_offset=summary_offset,
278
319
  )
@@ -1,5 +1,5 @@
1
1
  You are maintaining a context-aware state summary for an interactive agent.
2
- You will be given a list of events corresponding to actions taken by the agent, and the most recent previous summary if one exists.
2
+ You will be given a list of events corresponding to actions taken by the agent, which will include previous summaries.
3
3
  If the events being summarized contain ANY task-tracking, you MUST include a TASK_TRACKING section to maintain continuity.
4
4
  When referencing tasks make sure to preserve exact task IDs and statuses.
5
5
 
@@ -46,10 +46,6 @@ COMPLETED: 15 haikus written for results [T,H,T,H,T,H,T,T,H,T,H,T,H,T,H]
46
46
  PENDING: 5 more haikus needed
47
47
  CURRENT_STATE: Last flip: Heads, Haiku count: 15/20
48
48
 
49
- <PREVIOUS SUMMARY>
50
- {{ previous_summary }}
51
- </PREVIOUS SUMMARY>
52
-
53
49
  {% for event in events %}
54
50
  <EVENT>
55
51
  {{ event }}
@@ -27,9 +27,10 @@ You can also directly look up a skill's full content by reading its location pat
27
27
  <CUSTOM_SECRETS>
28
28
  ### Credential Access
29
29
  * Automatic secret injection: When you reference a registered secret key in your bash command, the secret value will be automatically exported as an environment variable before your command executes.
30
- * How to use secrets: Simply reference the secret key in your command (e.g., `echo ${GITHUB_TOKEN:0:8}` or `curl -H "Authorization: Bearer $API_KEY" https://api.example.com`). The system will detect the key name in your command text and export it as environment variable before it executes your command.
30
+ * How to use secrets: Simply reference the secret key in your command (e.g., `curl -H "Authorization: Bearer $API_KEY" https://api.example.com`). The system will detect the key name in your command text and export it as environment variable before it executes your command.
31
31
  * Secret detection: The system performs case-insensitive matching to find secret keys in your command text. If a registered secret key appears anywhere in your command, its value will be made available as an environment variable.
32
32
  * Security: Secret values are automatically masked in command output to prevent accidental exposure. You will see `<secret-hidden>` instead of the actual secret value in the output.
33
+ * Avoid exposing raw secrets: Never echo or print the full value of secrets (e.g., avoid `echo $SECRET`). The conversation history may be logged or shared, and exposing raw secret values could compromise security. Instead, use secrets directly in commands where they serve their intended purpose (e.g., in curl headers or git URLs).
33
34
  * Refreshing expired secrets: Some secrets (like GITHUB_TOKEN) may be updated periodically or expire over time. If a secret stops working (e.g., authentication failures), try using it again in a new command - the system should automatically use the refreshed value. For example, if GITHUB_TOKEN was used in a git remote URL and later expired, you can update the remote URL with the current token: `git remote set-url origin https://${GITHUB_TOKEN}@github.com/username/repo.git` to pick up the refreshed token value.
34
35
  * If it still fails, report it to the user.
35
36
 
@@ -27,15 +27,10 @@ from openhands.sdk.context.skills.utils import (
27
27
  validate_skill_name,
28
28
  )
29
29
  from openhands.sdk.logger import get_logger
30
- from openhands.sdk.utils import maybe_truncate
31
30
 
32
31
 
33
32
  logger = get_logger(__name__)
34
33
 
35
- # Maximum characters for third-party skill files (e.g., AGENTS.md, CLAUDE.md, GEMINI.md)
36
- # These files are always active, so we want to keep them reasonably sized
37
- THIRD_PARTY_SKILL_MAX_CHARS = 10_000
38
-
39
34
 
40
35
  class SkillInfo(BaseModel):
41
36
  """Lightweight representation of a skill's essential information.
@@ -485,32 +480,14 @@ class Skill(BaseModel):
485
480
  """Handle third-party skill files (e.g., .cursorrules, AGENTS.md).
486
481
 
487
482
  Creates a Skill with None trigger (always active) if the file type
488
- is recognized. Truncates content if it exceeds the limit.
483
+ is recognized.
489
484
  """
490
485
  skill_name = cls.PATH_TO_THIRD_PARTY_SKILL_NAME.get(path.name.lower())
491
486
 
492
487
  if skill_name is not None:
493
- truncated_content = maybe_truncate(
494
- file_content,
495
- truncate_after=THIRD_PARTY_SKILL_MAX_CHARS,
496
- truncate_notice=(
497
- f"\n\n<TRUNCATED><NOTE>The file {path} exceeded the "
498
- f"maximum length ({THIRD_PARTY_SKILL_MAX_CHARS} "
499
- f"characters) and has been truncated. Only the "
500
- f"beginning and end are shown. You can read the full "
501
- f"file if needed.</NOTE>\n\n"
502
- ),
503
- )
504
-
505
- if len(file_content) > THIRD_PARTY_SKILL_MAX_CHARS:
506
- logger.warning(
507
- f"Third-party skill file {path} ({len(file_content)} chars) "
508
- f"exceeded limit ({THIRD_PARTY_SKILL_MAX_CHARS} chars), truncating"
509
- )
510
-
511
488
  return Skill(
512
489
  name=skill_name,
513
- content=truncated_content,
490
+ content=file_content,
514
491
  source=str(path),
515
492
  trigger=None,
516
493
  )