openhands-sdk 1.49.4__tar.gz → 1.49.5__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 (306) hide show
  1. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/PKG-INFO +1 -1
  2. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/view/properties/tool_call_matching.py +4 -9
  3. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/secret_registry.py +64 -10
  4. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/extensions/installation/metadata.py +2 -2
  5. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/message.py +3 -2
  6. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/model_features.py +7 -0
  7. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/telemetry.py +127 -126
  8. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/mcp/tool.py +60 -2
  9. openhands_sdk-1.49.5/openhands/sdk/utils/masking.py +13 -0
  10. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands_sdk.egg-info/PKG-INFO +1 -1
  11. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands_sdk.egg-info/SOURCES.txt +2 -0
  12. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/pyproject.toml +1 -1
  13. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/__init__.py +0 -0
  14. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/__init__.py +0 -0
  15. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/acp_agent.py +0 -0
  16. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/acp_file_credentials.py +0 -0
  17. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/acp_models.py +0 -0
  18. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/acp_tracing.py +0 -0
  19. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/agent.py +0 -0
  20. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/base.py +0 -0
  21. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/critic_mixin.py +0 -0
  22. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/parallel_executor.py +0 -0
  23. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/response_dispatch.py +0 -0
  24. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/stream_context.py +0 -0
  25. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/agent/utils.py +0 -0
  26. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/banner.py +0 -0
  27. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/__init__.py +0 -0
  28. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/agent_context.py +0 -0
  29. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/condenser/__init__.py +0 -0
  30. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/condenser/base.py +0 -0
  31. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
  32. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  33. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  34. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
  35. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/condenser/utils.py +0 -0
  36. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/memory.py +0 -0
  37. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/prompts/__init__.py +0 -0
  38. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/prompts/presets.py +0 -0
  39. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/prompts/prompt.py +0 -0
  40. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/prompts/registry.py +0 -0
  41. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/prompts/section.py +0 -0
  42. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
  43. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
  44. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/prompts/sections/planning.py +0 -0
  45. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/prompts/sections/static.py +0 -0
  46. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  47. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  48. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/view/__init__.py +0 -0
  49. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/view/manipulation_indices.py +0 -0
  50. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/view/properties/__init__.py +0 -0
  51. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/view/properties/base.py +0 -0
  52. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
  53. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
  54. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
  55. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/context/view/view.py +0 -0
  56. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/__init__.py +0 -0
  57. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/base.py +0 -0
  58. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/cancellation.py +0 -0
  59. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/conversation.py +0 -0
  60. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/conversation_stats.py +0 -0
  61. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/event_store.py +0 -0
  62. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/events_list_base.py +0 -0
  63. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/exceptions.py +0 -0
  64. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/fifo_lock.py +0 -0
  65. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/goal/__init__.py +0 -0
  66. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/goal/controller.py +0 -0
  67. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/goal/judge.py +0 -0
  68. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/goal/prompts.py +0 -0
  69. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/goal/runner.py +0 -0
  70. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/impl/__init__.py +0 -0
  71. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/impl/local_conversation.py +0 -0
  72. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/impl/remote_conversation.py +0 -0
  73. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/persistence_const.py +0 -0
  74. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/request.py +0 -0
  75. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
  76. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/response_utils.py +0 -0
  77. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/state.py +0 -0
  78. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/stuck_detector.py +0 -0
  79. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/title_utils.py +0 -0
  80. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/types.py +0 -0
  81. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  82. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/visualizer/base.py +0 -0
  83. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/conversation/visualizer/default.py +0 -0
  84. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/credential.py +0 -0
  85. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/__init__.py +0 -0
  86. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/base.py +0 -0
  87. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/impl/__init__.py +0 -0
  88. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  89. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  90. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  91. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/impl/api/client.py +0 -0
  92. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/impl/api/critic.py +0 -0
  93. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  94. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  95. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  96. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/critic/result.py +0 -0
  97. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/__init__.py +0 -0
  98. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/acp_tool_call.py +0 -0
  99. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/base.py +0 -0
  100. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/condenser.py +0 -0
  101. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/conversation_error.py +0 -0
  102. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/conversation_state.py +0 -0
  103. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/error_classification.py +0 -0
  104. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/hook_execution.py +0 -0
  105. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/llm_completion_log.py +0 -0
  106. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  107. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/llm_convertible/action.py +0 -0
  108. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/llm_convertible/message.py +0 -0
  109. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  110. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
  111. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/llm_convertible/system.py +0 -0
  112. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/resume_transcript.py +0 -0
  113. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/streaming_delta.py +0 -0
  114. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/token.py +0 -0
  115. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/types.py +0 -0
  116. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/event/user_action.py +0 -0
  117. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/extensions/__init__.py +0 -0
  118. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/extensions/fetch.py +0 -0
  119. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/extensions/installation/__init__.py +0 -0
  120. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/extensions/installation/info.py +0 -0
  121. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/extensions/installation/interface.py +0 -0
  122. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/extensions/installation/manager.py +0 -0
  123. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/extensions/installation/utils.py +0 -0
  124. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/git/cached_repo.py +0 -0
  125. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/git/exceptions.py +0 -0
  126. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/git/git_changes.py +0 -0
  127. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/git/git_commits.py +0 -0
  128. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/git/git_diff.py +0 -0
  129. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/git/models.py +0 -0
  130. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/git/utils.py +0 -0
  131. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/hooks/__init__.py +0 -0
  132. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/hooks/config.py +0 -0
  133. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  134. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/hooks/executor.py +0 -0
  135. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/hooks/manager.py +0 -0
  136. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/hooks/types.py +0 -0
  137. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/io/__init__.py +0 -0
  138. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/io/base.py +0 -0
  139. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/io/cache.py +0 -0
  140. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/io/local.py +0 -0
  141. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/io/memory.py +0 -0
  142. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/__init__.py +0 -0
  143. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/auth/__init__.py +0 -0
  144. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/auth/credentials.py +0 -0
  145. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/auth/openai.py +0 -0
  146. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/cleanup_profile.py +0 -0
  147. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/exceptions/__init__.py +0 -0
  148. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/exceptions/classifier.py +0 -0
  149. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/exceptions/mapping.py +0 -0
  150. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/exceptions/types.py +0 -0
  151. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/fallback_strategy.py +0 -0
  152. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/llm.py +0 -0
  153. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/llm_profile_store.py +0 -0
  154. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/llm_registry.py +0 -0
  155. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/llm_response.py +0 -0
  156. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  157. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
  158. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  159. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/options/__init__.py +0 -0
  160. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/options/chat_options.py +0 -0
  161. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/options/common.py +0 -0
  162. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/options/responses_options.py +0 -0
  163. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/provider_connection_store.py +0 -0
  164. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/router/__init__.py +0 -0
  165. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/router/base.py +0 -0
  166. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  167. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/router/impl/random.py +0 -0
  168. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/streaming.py +0 -0
  169. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/image_inline.py +0 -0
  170. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/image_resize.py +0 -0
  171. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
  172. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/metrics.py +0 -0
  173. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/model_info.py +0 -0
  174. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  175. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
  176. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
  177. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
  178. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
  179. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
  180. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
  181. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  182. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/verified_models.py +0 -0
  183. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
  184. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/logger/__init__.py +0 -0
  185. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/logger/logger.py +0 -0
  186. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/logger/rolling.py +0 -0
  187. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/marketplace/__init__.py +0 -0
  188. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/marketplace/registration.py +0 -0
  189. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/marketplace/registry.py +0 -0
  190. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/marketplace/types.py +0 -0
  191. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/mcp/__init__.py +0 -0
  192. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/mcp/client.py +0 -0
  193. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/mcp/config.py +0 -0
  194. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/mcp/definition.py +0 -0
  195. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/mcp/exceptions.py +0 -0
  196. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/mcp/utils.py +0 -0
  197. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/observability/__init__.py +0 -0
  198. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/observability/laminar.py +0 -0
  199. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/observability/utils.py +0 -0
  200. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/__init__.py +0 -0
  201. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/discovery.py +0 -0
  202. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/fetch.py +0 -0
  203. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/format/__init__.py +0 -0
  204. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
  205. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
  206. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/format/base.py +0 -0
  207. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/format/claude_code.py +0 -0
  208. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
  209. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
  210. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/installed.py +0 -0
  211. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/loader.py +0 -0
  212. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/plugin.py +0 -0
  213. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/source.py +0 -0
  214. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/plugin/types.py +0 -0
  215. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/profiles/__init__.py +0 -0
  216. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/profiles/agent_profile.py +0 -0
  217. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/profiles/agent_profile_store.py +0 -0
  218. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/profiles/profile_refs.py +0 -0
  219. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/profiles/resolver.py +0 -0
  220. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/profiles/seed.py +0 -0
  221. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/py.typed +0 -0
  222. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/secret/__init__.py +0 -0
  223. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/secret/secrets.py +0 -0
  224. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/__init__.py +0 -0
  225. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/_shell_ast.py +0 -0
  226. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/analyzer.py +0 -0
  227. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/confirmation_policy.py +0 -0
  228. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
  229. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
  230. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
  231. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
  232. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
  233. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/ensemble.py +0 -0
  234. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/grayswan/__init__.py +0 -0
  235. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/grayswan/analyzer.py +0 -0
  236. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/grayswan/utils.py +0 -0
  237. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/llm_analyzer.py +0 -0
  238. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/risk.py +0 -0
  239. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/shell_parser.py +0 -0
  240. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/toolshield_helpers.py +0 -0
  241. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
  242. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/settings/__init__.py +0 -0
  243. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/settings/acp_install_catalog.py +0 -0
  244. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/settings/acp_providers.py +0 -0
  245. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/settings/api_models.py +0 -0
  246. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/settings/metadata.py +0 -0
  247. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/settings/model.py +0 -0
  248. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/skills/__init__.py +0 -0
  249. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/skills/exceptions.py +0 -0
  250. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/skills/execute.py +0 -0
  251. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/skills/fetch.py +0 -0
  252. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/skills/installed.py +0 -0
  253. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/skills/skill.py +0 -0
  254. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/skills/trigger.py +0 -0
  255. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/skills/types.py +0 -0
  256. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/skills/utils.py +0 -0
  257. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/subagent/__init__.py +0 -0
  258. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/subagent/load.py +0 -0
  259. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/subagent/registry.py +0 -0
  260. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/subagent/schema.py +0 -0
  261. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/testing/__init__.py +0 -0
  262. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/testing/test_llm.py +0 -0
  263. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/__init__.py +0 -0
  264. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/builtins/__init__.py +0 -0
  265. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/builtins/finish.py +0 -0
  266. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
  267. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
  268. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/builtins/think.py +0 -0
  269. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
  270. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/client_tool.py +0 -0
  271. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/defaults.py +0 -0
  272. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/registry.py +0 -0
  273. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/schema.py +0 -0
  274. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/spec.py +0 -0
  275. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/tool/tool.py +0 -0
  276. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/__init__.py +0 -0
  277. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/async_executor.py +0 -0
  278. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/async_utils.py +0 -0
  279. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/cipher.py +0 -0
  280. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/command.py +0 -0
  281. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/datetime.py +0 -0
  282. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/deprecation.py +0 -0
  283. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/files.py +0 -0
  284. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/github.py +0 -0
  285. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/json.py +0 -0
  286. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/models.py +0 -0
  287. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/paging.py +0 -0
  288. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/path.py +0 -0
  289. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  290. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/redact.py +0 -0
  291. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/truncate.py +0 -0
  292. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/utils/visualize.py +0 -0
  293. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/workspace/__init__.py +0 -0
  294. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/workspace/base.py +0 -0
  295. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/workspace/local.py +0 -0
  296. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/workspace/models.py +0 -0
  297. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/workspace/remote/__init__.py +0 -0
  298. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
  299. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/workspace/remote/base.py +0 -0
  300. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
  301. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/workspace/repo.py +0 -0
  302. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands/sdk/workspace/workspace.py +0 -0
  303. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  304. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands_sdk.egg-info/requires.txt +0 -0
  305. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/openhands_sdk.egg-info/top_level.txt +0 -0
  306. {openhands_sdk-1.49.4 → openhands_sdk-1.49.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.49.4
3
+ Version: 1.49.5
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
@@ -64,8 +64,9 @@ class ToolCallMatchingProperty(ViewPropertyBase):
64
64
  ) -> ManipulationIndices:
65
65
  """Calculate manipulation indices for tool call matching.
66
66
 
67
- This property is maintained by ensuring there are no manipulation indices
68
- between action events and their paired observation event.
67
+ Indices between an action and its matching observation are protected.
68
+ Observation-like events without a pending action do not affect boundaries;
69
+ property enforcement separately removes orphaned and duplicate observations.
69
70
  """
70
71
  # Start with a complete set of manipulation indices, then we'll remove those
71
72
  # between actions and their paired observations.
@@ -84,13 +85,7 @@ class ToolCallMatchingProperty(ViewPropertyBase):
84
85
  case ActionEvent():
85
86
  pending_tool_call_ids.add(event.tool_call_id)
86
87
  case ObservationBaseEvent():
87
- # Intentionally use remove(), not discard(): a second
88
- # observation-like event for the same tool_call_id means the
89
- # view has already violated the 1 action -> 1 result
90
- # invariant that downstream LLM APIs expect. That case must
91
- # be fixed by de-duplicating the view before serialization,
92
- # not by silently tolerating it here.
93
- pending_tool_call_ids.remove(event.tool_call_id)
88
+ pending_tool_call_ids.discard(event.tool_call_id)
94
89
 
95
90
  if pending_tool_call_ids:
96
91
  # The enumeration index corresponds to the position of the event, but we
@@ -11,6 +11,7 @@ from pydantic import BaseModel, Field, PrivateAttr, SecretStr
11
11
 
12
12
  from openhands.sdk.logger import get_logger
13
13
  from openhands.sdk.secret import SecretSource, SecretValue, StaticSecret
14
+ from openhands.sdk.utils.masking import PreserveDataUrls, SkipSecretMasking
14
15
  from openhands.sdk.utils.models import OpenHandsModel
15
16
 
16
17
 
@@ -20,8 +21,14 @@ logger = get_logger(__name__)
20
21
  FAILED_LOOKUP_RETRY_SECONDS: Final[float] = 60.0
21
22
 
22
23
 
23
- def _mask_value(value: Any, mask: Callable[[str], str]) -> Any:
24
- """Recursively mask every ``str`` reachable from ``value``."""
24
+ def _mask_value(
25
+ value: Any,
26
+ mask: Callable[[str], str],
27
+ *,
28
+ skip_secret_masking: bool = False,
29
+ preserve_data_urls: bool = False,
30
+ ) -> Any:
31
+ """Recursively mask strings, respecting opaque payload field metadata."""
25
32
  match value:
26
33
  case Enum():
27
34
  # A str-subclass enum is a str, but masking it would downgrade the
@@ -29,20 +36,56 @@ def _mask_value(value: Any, mask: Callable[[str], str]) -> Any:
29
36
  # vocabulary is fixed, so it can never hold a secret anyway.
30
37
  return value
31
38
  case str():
39
+ if skip_secret_masking:
40
+ return value
41
+ if preserve_data_urls and value.startswith("data:") and ";base64," in value:
42
+ return value
32
43
  return mask(value)
33
44
  case BaseModel():
34
45
  return _mask_model(value, mask)
35
46
  case list():
36
- return [_mask_value(item, mask) for item in value]
47
+ return [
48
+ _mask_value(
49
+ item,
50
+ mask,
51
+ skip_secret_masking=skip_secret_masking,
52
+ preserve_data_urls=preserve_data_urls,
53
+ )
54
+ for item in value
55
+ ]
37
56
  case tuple():
38
- items = [_mask_value(item, mask) for item in value]
57
+ items = [
58
+ _mask_value(
59
+ item,
60
+ mask,
61
+ skip_secret_masking=skip_secret_masking,
62
+ preserve_data_urls=preserve_data_urls,
63
+ )
64
+ for item in value
65
+ ]
39
66
  # Rebuild a NamedTuple through its own constructor; tuple(items)
40
67
  # would downgrade it the same way masking an Enum member does.
41
68
  return type(value)(*items) if hasattr(value, "_fields") else tuple(items)
42
69
  case set() | frozenset():
43
- return type(value)(_mask_value(item, mask) for item in value)
70
+ return type(value)(
71
+ _mask_value(
72
+ item,
73
+ mask,
74
+ skip_secret_masking=skip_secret_masking,
75
+ preserve_data_urls=preserve_data_urls,
76
+ )
77
+ for item in value
78
+ )
44
79
  case dict():
45
- return {key: _mask_value(item, mask) for key, item in value.items()}
80
+ return {
81
+ key: _mask_value(
82
+ item,
83
+ mask,
84
+ skip_secret_masking=skip_secret_masking,
85
+ preserve_data_urls=preserve_data_urls,
86
+ )
87
+ for key, item in value.items()
88
+ }
46
89
  case _:
47
90
  return value
48
91
 
@@ -53,10 +96,21 @@ def _mask_model[ModelT: BaseModel](model: ModelT, mask: Callable[[str], str]) ->
53
96
  ``model_copy`` is used rather than a validate round-trip so private
54
97
  attributes survive and no field is re-coerced.
55
98
  """
56
- updates = {
57
- name: _mask_value(getattr(model, name), mask)
58
- for name in type(model).model_fields
59
- }
99
+ updates = {}
100
+ for name, field in type(model).model_fields.items():
101
+ metadata = field.metadata
102
+ skip_secret_masking = any(
103
+ isinstance(item, SkipSecretMasking) for item in metadata
104
+ )
105
+ preserve_data_urls = any(
106
+ isinstance(item, PreserveDataUrls) for item in metadata
107
+ )
108
+ updates[name] = _mask_value(
109
+ getattr(model, name),
110
+ mask,
111
+ skip_secret_masking=skip_secret_masking,
112
+ preserve_data_urls=preserve_data_urls,
113
+ )
60
114
  return model.model_copy(update=updates)
61
115
 
62
116
 
@@ -150,7 +150,7 @@ class InstallationMetadata(BaseModel):
150
150
  return cls()
151
151
 
152
152
  try:
153
- return cls.model_validate_json(metadata_path.read_text())
153
+ return cls.model_validate_json(metadata_path.read_text(encoding="utf-8"))
154
154
  except Exception as e:
155
155
  logger.warning(f"Failed to load installed extension metadata: {e}")
156
156
  return cls()
@@ -159,7 +159,7 @@ class InstallationMetadata(BaseModel):
159
159
  """Save metadata to the installed extensions directory."""
160
160
  metadata_path = self.get_metadata_path(installed_dir)
161
161
  metadata_path.parent.mkdir(parents=True, exist_ok=True)
162
- metadata_path.write_text(self.model_dump_json(indent=2))
162
+ metadata_path.write_text(self.model_dump_json(indent=2), encoding="utf-8")
163
163
 
164
164
  def validate_tracked(self, installed_dir: Path) -> list[InstallationInfo]:
165
165
  """Validate tracked extensions exist on disk.
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  from abc import abstractmethod
3
3
  from collections.abc import Sequence
4
- from typing import Any, ClassVar, Literal
4
+ from typing import Annotated, Any, ClassVar, Literal
5
5
 
6
6
  from litellm import ChatCompletionMessageToolCall, ResponseFunctionToolCall
7
7
  from litellm.types.responses.main import (
@@ -16,6 +16,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_valida
16
16
  from openhands.sdk.logger import get_logger
17
17
  from openhands.sdk.utils import DEFAULT_TEXT_CONTENT_LIMIT, maybe_truncate
18
18
  from openhands.sdk.utils.deprecation import handle_deprecated_model_fields
19
+ from openhands.sdk.utils.masking import PreserveDataUrls
19
20
 
20
21
 
21
22
  logger = get_logger(__name__)
@@ -203,7 +204,7 @@ class TextContent(BaseContent):
203
204
 
204
205
  class ImageContent(BaseContent):
205
206
  type: Literal["image"] = "image"
206
- image_urls: list[str]
207
+ image_urls: Annotated[list[str], PreserveDataUrls()]
207
208
 
208
209
  def to_llm_dict(self) -> list[dict[str, str | dict[str, str]]]:
209
210
  """Convert to LLM API format."""
@@ -109,6 +109,7 @@ def _normalized_supported_openai_params(model: str | None) -> frozenset[str]:
109
109
 
110
110
 
111
111
  REASONING_EFFORT_MODEL_OVERRIDES = {
112
+ "gpt-5.2-codex": "gpt-5.2-codex",
112
113
  "kimi-k3": "moonshot/kimi-k3",
113
114
  }
114
115
 
@@ -188,9 +189,15 @@ SUPPORTS_STOP_WORDS_FALSE_MODELS: list[str] = [
188
189
  ]
189
190
 
190
191
  # Models that should use the OpenAI Responses API path by default
192
+ # NOTE: model_matches uses case-insensitive substring matching, so a bare family
193
+ # token like "gpt-5" / "gpt-6" covers all variants (mini, sol, luna, astra, ...).
191
194
  RESPONSES_API_MODELS: list[str] = [
192
195
  # OpenAI GPT-5 family (includes mini variants)
193
196
  "gpt-5",
197
+ # OpenAI GPT-6 family (gpt-6-sol, gpt-6-luna, gpt-6-astra, ...). These reject
198
+ # function tools + reasoning_effort on /v1/chat/completions; /v1/responses
199
+ # supports both. See saas-deploy #1144.
200
+ "gpt-6",
194
201
  # OpenAI Codex (uses Responses API)
195
202
  "codex-mini-latest",
196
203
  ]
@@ -5,6 +5,7 @@ import traceback
5
5
  import uuid
6
6
  import warnings
7
7
  from collections.abc import Callable
8
+ from dataclasses import dataclass
8
9
  from typing import Any, ClassVar
9
10
 
10
11
  from litellm.cost_calculator import completion_cost as litellm_completion_cost
@@ -21,6 +22,109 @@ from openhands.sdk.logger import get_logger
21
22
  logger = get_logger(__name__)
22
23
 
23
24
 
25
+ @dataclass(frozen=True, slots=True)
26
+ class UsageSnapshot:
27
+ """Provider-independent view of one response's token accounting.
28
+
29
+ LiteLLM reports usage in two disjoint shapes that name the same quantities
30
+ differently: ``Usage`` (Chat Completions) uses ``prompt_tokens`` /
31
+ ``completion_tokens``, while ``ResponseAPIUsage`` (Responses API) uses
32
+ ``input_tokens`` / ``output_tokens``. Normalizing once, at the boundary,
33
+ keeps every consumer -- metrics, span attributes and completion logs -- on
34
+ a single vocabulary instead of rediscovering the aliases at each call site.
35
+ """
36
+
37
+ prompt_tokens: int = 0
38
+ completion_tokens: int = 0
39
+ reasoning_tokens: int = 0
40
+ cache_read_tokens: int = 0
41
+ cache_write_tokens: int = 0
42
+
43
+ @property
44
+ def has_tokens(self) -> bool:
45
+ """Whether the provider reported any prompt or completion tokens."""
46
+ return self.prompt_tokens > 0 or self.completion_tokens > 0
47
+
48
+
49
+ def normalize_usage(usage: Usage | ResponseAPIUsage | None) -> UsageSnapshot | None:
50
+ """Return a :class:`UsageSnapshot` for either provider usage shape.
51
+
52
+ ``None`` means "no token accounting": the provider reported no usage, or
53
+ reported a shape this module does not recognize. Unrecognized shapes are
54
+ ignored rather than guessed at, so adding a provider cannot silently feed
55
+ invented token counts into cost and cache accounting.
56
+ """
57
+ if isinstance(usage, Usage):
58
+ prompt_details = usage.prompt_tokens_details
59
+ completion_details = usage.completion_tokens_details
60
+ # ``cache_creation_tokens`` defaults to ``None``, so presence in
61
+ # ``model_fields_set`` is what distinguishes "provider reported a cache
62
+ # write" from "provider said nothing about cache writes".
63
+ cache_write = 0
64
+ if (
65
+ prompt_details is not None
66
+ and "cache_creation_tokens" in prompt_details.model_fields_set
67
+ ):
68
+ cache_write = int(prompt_details.cache_creation_tokens or 0)
69
+ return UsageSnapshot(
70
+ prompt_tokens=int(usage.prompt_tokens or 0),
71
+ completion_tokens=int(usage.completion_tokens or 0),
72
+ reasoning_tokens=(
73
+ int(completion_details.reasoning_tokens or 0)
74
+ if completion_details is not None
75
+ else 0
76
+ ),
77
+ cache_read_tokens=(
78
+ int(prompt_details.cached_tokens or 0)
79
+ if prompt_details is not None
80
+ else 0
81
+ ),
82
+ cache_write_tokens=cache_write,
83
+ )
84
+
85
+ if isinstance(usage, ResponseAPIUsage):
86
+ input_details = usage.input_tokens_details
87
+ output_details = usage.output_tokens_details
88
+ return UsageSnapshot(
89
+ prompt_tokens=int(usage.input_tokens or 0),
90
+ completion_tokens=int(usage.output_tokens or 0),
91
+ reasoning_tokens=(
92
+ int(output_details.reasoning_tokens or 0)
93
+ if output_details is not None
94
+ else 0
95
+ ),
96
+ cache_read_tokens=(
97
+ int(input_details.cached_tokens or 0)
98
+ if input_details is not None
99
+ else 0
100
+ ),
101
+ )
102
+
103
+ return None
104
+
105
+
106
+ def _response_usage(
107
+ resp: ModelResponse | ResponsesAPIResponse,
108
+ ) -> Usage | ResponseAPIUsage | None:
109
+ """Return the usage object a response carries, if any.
110
+
111
+ ``ResponsesAPIResponse`` declares ``usage`` as a field. ``ModelResponse``
112
+ only accepts it as a constructor argument, so pydantic keeps it in the
113
+ model's extra mapping instead of its fields; reading that mapping is the
114
+ typed equivalent of probing for a possibly-absent attribute.
115
+ """
116
+ if isinstance(resp, ResponsesAPIResponse):
117
+ return resp.usage
118
+ if isinstance(resp, ModelResponse):
119
+ extra = resp.model_extra
120
+ if not extra:
121
+ return None
122
+ usage = extra.get("usage")
123
+ if isinstance(usage, (Usage, ResponseAPIUsage)):
124
+ return usage
125
+ return None
126
+
127
+
24
128
  class Telemetry(BaseModel):
25
129
  """
26
130
  Handles latency, token/cost accounting, and optional logging.
@@ -106,10 +210,10 @@ class Telemetry(BaseModel):
106
210
  if cost:
107
211
  self.metrics.add_cost(cost)
108
212
 
109
- # 3) tokens - use typed usage field when available
110
- usage = getattr(resp, "usage", None)
213
+ # 3) tokens - normalize the provider's usage shape exactly once
214
+ usage = normalize_usage(_response_usage(resp))
111
215
 
112
- if usage and self._has_meaningful_usage(usage):
216
+ if usage is not None and usage.has_tokens:
113
217
  self._record_usage(
114
218
  usage, response_id, self._req_ctx.get("context_window", 0)
115
219
  )
@@ -176,96 +280,20 @@ class Telemetry(BaseModel):
176
280
  return
177
281
 
178
282
  # ---------- Helpers ----------
179
- def _has_meaningful_usage(self, usage: Usage | ResponseAPIUsage | None) -> bool:
180
- """Check if usage has meaningful (non-zero) token counts.
181
-
182
- Supports both Chat Completions Usage and Responses API Usage shapes.
183
- """
184
- if usage is None:
185
- return False
186
- try:
187
- prompt_tokens = getattr(usage, "prompt_tokens", None)
188
- if prompt_tokens is None:
189
- prompt_tokens = getattr(usage, "input_tokens", 0)
190
- completion_tokens = getattr(usage, "completion_tokens", None)
191
- if completion_tokens is None:
192
- completion_tokens = getattr(usage, "output_tokens", 0)
193
-
194
- pt = int(prompt_tokens or 0)
195
- ct = int(completion_tokens or 0)
196
- return pt > 0 or ct > 0
197
- except Exception:
198
- return False
199
-
200
283
  def _record_usage(
201
- self, usage: Usage | ResponseAPIUsage, response_id: str, context_window: int
284
+ self, usage: UsageSnapshot, response_id: str, context_window: int
202
285
  ) -> None:
203
- """
204
- Record token usage, supporting both Chat Completions Usage and
205
- Responses API Usage.
206
-
207
- Chat shape:
208
- - prompt_tokens, completion_tokens
209
- - prompt_tokens_details.cached_tokens
210
- - completion_tokens_details.reasoning_tokens
211
- - _cache_creation_input_tokens for cache_write
212
- Responses shape:
213
- - input_tokens, output_tokens
214
- - input_tokens_details.cached_tokens
215
- - output_tokens_details.reasoning_tokens
216
- """
217
- prompt_tokens = int(
218
- getattr(usage, "prompt_tokens", None)
219
- or getattr(usage, "input_tokens", 0)
220
- or 0
221
- )
222
- completion_tokens = int(
223
- getattr(usage, "completion_tokens", None)
224
- or getattr(usage, "output_tokens", 0)
225
- or 0
226
- )
227
-
228
- cache_read, cache_write = self._cache_buckets(usage)
229
-
230
- reasoning_tokens = 0
231
- c_details = getattr(usage, "completion_tokens_details", None) or getattr(
232
- usage, "output_tokens_details", None
233
- )
234
- if c_details is not None:
235
- reasoning_tokens = int(getattr(c_details, "reasoning_tokens", 0) or 0)
236
-
286
+ """Record an already-normalized usage snapshot into ``metrics``."""
237
287
  self.metrics.add_token_usage(
238
- prompt_tokens=prompt_tokens,
239
- completion_tokens=completion_tokens,
240
- cache_read_tokens=cache_read,
241
- cache_write_tokens=cache_write,
242
- reasoning_tokens=reasoning_tokens,
288
+ prompt_tokens=usage.prompt_tokens,
289
+ completion_tokens=usage.completion_tokens,
290
+ cache_read_tokens=usage.cache_read_tokens,
291
+ cache_write_tokens=usage.cache_write_tokens,
292
+ reasoning_tokens=usage.reasoning_tokens,
243
293
  context_window=context_window,
244
294
  response_id=response_id,
245
295
  )
246
296
 
247
- @staticmethod
248
- def _cache_buckets(usage: Usage | ResponseAPIUsage) -> tuple[int, int]:
249
- """Return ``(cache_read, cache_write)`` for either usage shape.
250
-
251
- Single source of truth for both ``metrics`` and the span, so the trace
252
- and the app's cost cannot disagree about the buckets.
253
- """
254
- if isinstance(usage, Usage):
255
- details = usage.prompt_tokens_details
256
- if details is None:
257
- return 0, 0
258
- cache_write = (
259
- details.cache_creation_tokens
260
- if "cache_creation_tokens" in details.model_fields_set
261
- else 0
262
- )
263
- return int(details.cached_tokens or 0), int(cache_write or 0)
264
-
265
- details = usage.input_tokens_details
266
- cache_read = details.cached_tokens if details is not None else 0
267
- return int(cache_read or 0), 0
268
-
269
297
  # ---------- Observability span ----------
270
298
  # These bracket one LLM call: ``on_request`` -> transport -> ``on_response``
271
299
  # all run inside a single retry attempt, so the span is current while
@@ -284,9 +312,7 @@ class Telemetry(BaseModel):
284
312
  logger.debug("Failed to open LLM span", exc_info=True)
285
313
  self._span = self._span_cm = None
286
314
 
287
- def _annotate_span(
288
- self, cost: float | None, usage: Usage | ResponseAPIUsage | None
289
- ) -> None:
315
+ def _annotate_span(self, cost: float | None, usage: UsageSnapshot | None) -> None:
290
316
  span = self._span
291
317
  if span is None:
292
318
  return
@@ -297,13 +323,15 @@ class Telemetry(BaseModel):
297
323
  # and priced by the real backend rather than by a name lookup.
298
324
  span.set_attribute("gen_ai.usage.cost", float(cost))
299
325
  if usage is not None:
300
- cache_read, cache_write = self._cache_buckets(usage)
301
326
  # Emitted unconditionally: lmnr only reports these when
302
327
  # prompt_tokens_details is populated, which not every provider
303
328
  # shape does.
304
- span.set_attribute("gen_ai.usage.cache_read_input_tokens", cache_read)
305
329
  span.set_attribute(
306
- "gen_ai.usage.cache_creation_input_tokens", cache_write
330
+ "gen_ai.usage.cache_read_input_tokens", usage.cache_read_tokens
331
+ )
332
+ span.set_attribute(
333
+ "gen_ai.usage.cache_creation_input_tokens",
334
+ usage.cache_write_tokens,
307
335
  )
308
336
  except Exception:
309
337
  logger.debug("Failed to annotate LLM span", exc_info=True)
@@ -339,7 +367,7 @@ class Telemetry(BaseModel):
339
367
  extra_kwargs["custom_cost_per_token"] = cost_per_token
340
368
 
341
369
  try:
342
- hidden = getattr(resp, "_hidden_params", {}) or {}
370
+ hidden = resp._hidden_params or {}
343
371
  cost = hidden.get("additional_headers", {}).get(
344
372
  "llm_provider-x-litellm-response-cost"
345
373
  )
@@ -391,40 +419,13 @@ class Telemetry(BaseModel):
391
419
 
392
420
  # Usage summary (prompt, completion, reasoning tokens) for quick inspection
393
421
  try:
394
- usage = getattr(resp, "usage", None)
395
- if usage:
396
- prompt_tokens = int(
397
- getattr(usage, "prompt_tokens", None)
398
- or getattr(usage, "input_tokens", 0)
399
- or 0
400
- )
401
- completion_tokens = int(
402
- getattr(usage, "completion_tokens", None)
403
- or getattr(usage, "output_tokens", 0)
404
- or 0
405
- )
406
- details = getattr(
407
- usage, "completion_tokens_details", None
408
- ) or getattr(usage, "output_tokens_details", None)
409
- reasoning_tokens = (
410
- int(getattr(details, "reasoning_tokens", 0) or 0)
411
- if details
412
- else 0
413
- )
414
- p_details = getattr(
415
- usage, "prompt_tokens_details", None
416
- ) or getattr(usage, "input_tokens_details", None)
417
- cache_read_tokens = (
418
- int(getattr(p_details, "cached_tokens", 0) or 0)
419
- if p_details
420
- else 0
421
- )
422
-
422
+ usage = normalize_usage(_response_usage(resp))
423
+ if usage is not None:
423
424
  data["usage_summary"] = {
424
- "prompt_tokens": prompt_tokens,
425
- "completion_tokens": completion_tokens,
426
- "reasoning_tokens": reasoning_tokens,
427
- "cache_read_tokens": cache_read_tokens,
425
+ "prompt_tokens": usage.prompt_tokens,
426
+ "completion_tokens": usage.completion_tokens,
427
+ "reasoning_tokens": usage.reasoning_tokens,
428
+ "cache_read_tokens": usage.cache_read_tokens,
428
429
  }
429
430
  except Exception:
430
431
  # Best-effort only; don't fail logging
@@ -15,7 +15,13 @@ if TYPE_CHECKING:
15
15
  import mcp.types
16
16
  from litellm import ChatCompletionToolParam
17
17
  from openai.types.responses import FunctionToolParam
18
- from pydantic import Field, ValidationError
18
+ from pydantic import (
19
+ Field,
20
+ SerializerFunctionWrapHandler,
21
+ ValidationError,
22
+ field_serializer,
23
+ field_validator,
24
+ )
19
25
 
20
26
  from openhands.sdk.llm import TextContent
21
27
  from openhands.sdk.logger import get_logger
@@ -42,6 +48,44 @@ logger = get_logger(__name__)
42
48
  # Default timeout for MCP tool execution in seconds
43
49
  MCP_TOOL_TIMEOUT_SECONDS = 300
44
50
 
51
+ # mcp 2.x dumps its snake_case attribute names unless by_alias=True; mcp 1.x
52
+ # only reads the camelCase wire names. Keys inside inputSchema/outputSchema
53
+ # are user JSON Schema and must never be renamed.
54
+ _MCP_TOOL_WIRE_KEYS: Final[dict[str, str]] = {
55
+ "input_schema": "inputSchema",
56
+ "output_schema": "outputSchema",
57
+ "meta": "_meta",
58
+ }
59
+ _MCP_NESTED_WIRE_KEYS: Final[dict[str, dict[str, str]]] = {
60
+ "annotations": {
61
+ "read_only_hint": "readOnlyHint",
62
+ "destructive_hint": "destructiveHint",
63
+ "idempotent_hint": "idempotentHint",
64
+ "open_world_hint": "openWorldHint",
65
+ },
66
+ "execution": {"task_support": "taskSupport"},
67
+ "icons": {"mime_type": "mimeType"},
68
+ }
69
+
70
+
71
+ def _mcp_tool_to_wire_keys(data: Any) -> Any:
72
+ """Normalize a serialized mcp.types.Tool to the MCP spec's camelCase keys."""
73
+ if not isinstance(data, dict):
74
+ return data
75
+ out = {_MCP_TOOL_WIRE_KEYS.get(k, k): v for k, v in data.items()}
76
+ for key, renames in _MCP_NESTED_WIRE_KEYS.items():
77
+ value = out.get(key)
78
+ if isinstance(value, dict):
79
+ out[key] = {renames.get(k, k): v for k, v in value.items()}
80
+ elif isinstance(value, list):
81
+ out[key] = [
82
+ {renames.get(k, k): v for k, v in item.items()}
83
+ if isinstance(item, dict)
84
+ else item
85
+ for item in value
86
+ ]
87
+ return out
88
+
45
89
 
46
90
  # NOTE: We don't define MCPToolAction because it
47
91
  # will be a pydantic BaseModel dynamically created from the MCP tool schema.
@@ -244,6 +288,20 @@ class MCPToolDefinition(ToolDefinition[MCPToolAction, MCPToolObservation]):
244
288
 
245
289
  mcp_tool: mcp.types.Tool = Field(description="The MCP tool definition.")
246
290
 
291
+ @field_validator("mcp_tool", mode="before")
292
+ @classmethod
293
+ def _read_either_mcp_spelling(cls, v: Any) -> Any:
294
+ return _mcp_tool_to_wire_keys(v)
295
+
296
+ @field_serializer("mcp_tool", mode="wrap")
297
+ def _write_mcp_wire_spelling(
298
+ self, v: mcp.types.Tool, handler: SerializerFunctionWrapHandler
299
+ ):
300
+ # Persisted events outlive the resolved mcp major; always write the
301
+ # spec's wire names so every mcp version can read them back. No return
302
+ # annotation, so the OpenAPI schema keeps referencing mcp.types.Tool.
303
+ return _mcp_tool_to_wire_keys(handler(v))
304
+
247
305
  @property
248
306
  def name(self) -> str: # type: ignore[override]
249
307
  """Return the MCP tool name instead of the class name."""
@@ -333,7 +391,7 @@ class MCPToolDefinition(ToolDefinition[MCPToolAction, MCPToolObservation]):
333
391
  try:
334
392
  annotations = (
335
393
  ToolAnnotations.model_validate(
336
- mcp_tool.annotations.model_dump(exclude_none=True)
394
+ mcp_tool.annotations.model_dump(exclude_none=True, by_alias=True)
337
395
  )
338
396
  if mcp_tool.annotations
339
397
  else None
@@ -0,0 +1,13 @@
1
+ """Field metadata used by secret masking."""
2
+
3
+
4
+ class SkipSecretMasking:
5
+ """Mark a field whose value must not be secret-masked."""
6
+
7
+ __slots__ = ()
8
+
9
+
10
+ class PreserveDataUrls:
11
+ """Mark string values that contain opaque base64 data URLs."""
12
+
13
+ __slots__ = ()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.49.4
3
+ Version: 1.49.5
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
@@ -279,6 +279,7 @@ pyproject.toml
279
279
  ./openhands/sdk/utils/files.py
280
280
  ./openhands/sdk/utils/github.py
281
281
  ./openhands/sdk/utils/json.py
282
+ ./openhands/sdk/utils/masking.py
282
283
  ./openhands/sdk/utils/models.py
283
284
  ./openhands/sdk/utils/paging.py
284
285
  ./openhands/sdk/utils/path.py
@@ -576,6 +577,7 @@ openhands/sdk/utils/deprecation.py
576
577
  openhands/sdk/utils/files.py
577
578
  openhands/sdk/utils/github.py
578
579
  openhands/sdk/utils/json.py
580
+ openhands/sdk/utils/masking.py
579
581
  openhands/sdk/utils/models.py
580
582
  openhands/sdk/utils/paging.py
581
583
  openhands/sdk/utils/path.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openhands-sdk"
3
- version = "1.49.4"
3
+ version = "1.49.5"
4
4
  description = "OpenHands SDK - Core functionality for building AI agents"
5
5
 
6
6
  requires-python = ">=3.12"