openhands-sdk 1.49.4__tar.gz → 1.49.6__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 (309) hide show
  1. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/PKG-INFO +1 -1
  2. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/tool_call_matching.py +4 -9
  3. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/impl/local_conversation.py +31 -0
  4. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/secret_registry.py +64 -10
  5. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/metadata.py +2 -2
  6. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/hooks/executor.py +4 -3
  7. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/__init__.py +10 -0
  8. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm.py +150 -0
  9. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/message.py +3 -2
  10. openhands_sdk-1.49.6/openhands/sdk/llm/meta_profile_store.py +274 -0
  11. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/model_features.py +6 -0
  12. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/telemetry.py +127 -126
  13. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/verified_models.py +7 -3
  14. openhands_sdk-1.49.6/openhands/sdk/mcp/oauth.py +85 -0
  15. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/mcp/tool.py +60 -2
  16. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/mcp/utils.py +3 -2
  17. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/settings/api_models.py +9 -0
  18. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/settings/model.py +66 -1
  19. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/__init__.py +11 -0
  20. openhands_sdk-1.49.6/openhands/sdk/tool/builtins/classify_and_switch_llm.py +503 -0
  21. openhands_sdk-1.49.6/openhands/sdk/utils/masking.py +13 -0
  22. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/PKG-INFO +1 -1
  23. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/SOURCES.txt +8 -0
  24. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/pyproject.toml +1 -1
  25. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/__init__.py +0 -0
  26. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/__init__.py +0 -0
  27. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_agent.py +0 -0
  28. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_file_credentials.py +0 -0
  29. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_models.py +0 -0
  30. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_tracing.py +0 -0
  31. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/agent.py +0 -0
  32. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/base.py +0 -0
  33. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/critic_mixin.py +0 -0
  34. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/parallel_executor.py +0 -0
  35. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/response_dispatch.py +0 -0
  36. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/stream_context.py +0 -0
  37. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/agent/utils.py +0 -0
  38. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/banner.py +0 -0
  39. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/__init__.py +0 -0
  40. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/agent_context.py +0 -0
  41. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/__init__.py +0 -0
  42. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/base.py +0 -0
  43. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
  44. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  45. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  46. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
  47. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/utils.py +0 -0
  48. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/memory.py +0 -0
  49. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/__init__.py +0 -0
  50. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/presets.py +0 -0
  51. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/prompt.py +0 -0
  52. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/registry.py +0 -0
  53. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/section.py +0 -0
  54. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
  55. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
  56. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/planning.py +0 -0
  57. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/static.py +0 -0
  58. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  59. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  60. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/view/__init__.py +0 -0
  61. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/view/manipulation_indices.py +0 -0
  62. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/__init__.py +0 -0
  63. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/base.py +0 -0
  64. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
  65. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
  66. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
  67. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/context/view/view.py +0 -0
  68. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/__init__.py +0 -0
  69. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/base.py +0 -0
  70. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/cancellation.py +0 -0
  71. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/conversation.py +0 -0
  72. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/conversation_stats.py +0 -0
  73. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/event_store.py +0 -0
  74. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/events_list_base.py +0 -0
  75. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/exceptions.py +0 -0
  76. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/fifo_lock.py +0 -0
  77. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/__init__.py +0 -0
  78. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/controller.py +0 -0
  79. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/judge.py +0 -0
  80. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/prompts.py +0 -0
  81. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/runner.py +0 -0
  82. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/impl/__init__.py +0 -0
  83. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/impl/remote_conversation.py +0 -0
  84. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/persistence_const.py +0 -0
  85. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/request.py +0 -0
  86. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
  87. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/response_utils.py +0 -0
  88. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/state.py +0 -0
  89. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/stuck_detector.py +0 -0
  90. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/title_utils.py +0 -0
  91. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/types.py +0 -0
  92. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  93. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/visualizer/base.py +0 -0
  94. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/conversation/visualizer/default.py +0 -0
  95. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/credential.py +0 -0
  96. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/__init__.py +0 -0
  97. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/base.py +0 -0
  98. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/__init__.py +0 -0
  99. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  100. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  101. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  102. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/client.py +0 -0
  103. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/critic.py +0 -0
  104. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  105. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  106. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  107. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/critic/result.py +0 -0
  108. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/__init__.py +0 -0
  109. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/acp_tool_call.py +0 -0
  110. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/base.py +0 -0
  111. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/condenser.py +0 -0
  112. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/conversation_error.py +0 -0
  113. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/conversation_state.py +0 -0
  114. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/error_classification.py +0 -0
  115. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/hook_execution.py +0 -0
  116. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_completion_log.py +0 -0
  117. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  118. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/action.py +0 -0
  119. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/message.py +0 -0
  120. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  121. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
  122. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/system.py +0 -0
  123. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/resume_transcript.py +0 -0
  124. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/streaming_delta.py +0 -0
  125. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/token.py +0 -0
  126. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/types.py +0 -0
  127. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/event/user_action.py +0 -0
  128. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/extensions/__init__.py +0 -0
  129. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/extensions/fetch.py +0 -0
  130. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/__init__.py +0 -0
  131. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/info.py +0 -0
  132. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/interface.py +0 -0
  133. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/manager.py +0 -0
  134. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/utils.py +0 -0
  135. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/git/cached_repo.py +0 -0
  136. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/git/exceptions.py +0 -0
  137. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/git/git_changes.py +0 -0
  138. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/git/git_commits.py +0 -0
  139. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/git/git_diff.py +0 -0
  140. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/git/models.py +0 -0
  141. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/git/utils.py +0 -0
  142. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/hooks/__init__.py +0 -0
  143. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/hooks/config.py +0 -0
  144. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  145. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/hooks/manager.py +0 -0
  146. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/hooks/types.py +0 -0
  147. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/io/__init__.py +0 -0
  148. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/io/base.py +0 -0
  149. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/io/cache.py +0 -0
  150. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/io/local.py +0 -0
  151. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/io/memory.py +0 -0
  152. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/auth/__init__.py +0 -0
  153. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/auth/credentials.py +0 -0
  154. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/auth/openai.py +0 -0
  155. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/cleanup_profile.py +0 -0
  156. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/__init__.py +0 -0
  157. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/classifier.py +0 -0
  158. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/mapping.py +0 -0
  159. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/types.py +0 -0
  160. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/fallback_strategy.py +0 -0
  161. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm_profile_store.py +0 -0
  162. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm_registry.py +0 -0
  163. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm_response.py +0 -0
  164. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  165. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
  166. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  167. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/__init__.py +0 -0
  168. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/chat_options.py +0 -0
  169. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/common.py +0 -0
  170. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/responses_options.py +0 -0
  171. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/provider_connection_store.py +0 -0
  172. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/__init__.py +0 -0
  173. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/base.py +0 -0
  174. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  175. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/impl/random.py +0 -0
  176. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/streaming.py +0 -0
  177. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/image_inline.py +0 -0
  178. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/image_resize.py +0 -0
  179. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
  180. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/metrics.py +0 -0
  181. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/model_info.py +0 -0
  182. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  183. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
  184. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
  185. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
  186. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
  187. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
  188. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
  189. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  190. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
  191. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/logger/__init__.py +0 -0
  192. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/logger/logger.py +0 -0
  193. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/logger/rolling.py +0 -0
  194. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/__init__.py +0 -0
  195. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/registration.py +0 -0
  196. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/registry.py +0 -0
  197. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/types.py +0 -0
  198. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/mcp/__init__.py +0 -0
  199. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/mcp/client.py +0 -0
  200. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/mcp/config.py +0 -0
  201. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/mcp/definition.py +0 -0
  202. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/mcp/exceptions.py +0 -0
  203. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/observability/__init__.py +0 -0
  204. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/observability/laminar.py +0 -0
  205. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/observability/utils.py +0 -0
  206. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/__init__.py +0 -0
  207. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/discovery.py +0 -0
  208. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/fetch.py +0 -0
  209. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/__init__.py +0 -0
  210. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
  211. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
  212. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/base.py +0 -0
  213. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/claude_code.py +0 -0
  214. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
  215. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
  216. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/installed.py +0 -0
  217. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/loader.py +0 -0
  218. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/plugin.py +0 -0
  219. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/source.py +0 -0
  220. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/plugin/types.py +0 -0
  221. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/profiles/__init__.py +0 -0
  222. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/profiles/agent_profile.py +0 -0
  223. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/profiles/agent_profile_store.py +0 -0
  224. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/profiles/profile_refs.py +0 -0
  225. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/profiles/resolver.py +0 -0
  226. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/profiles/seed.py +0 -0
  227. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/py.typed +0 -0
  228. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/secret/__init__.py +0 -0
  229. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/secret/secrets.py +0 -0
  230. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/__init__.py +0 -0
  231. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/_shell_ast.py +0 -0
  232. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/analyzer.py +0 -0
  233. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/confirmation_policy.py +0 -0
  234. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
  235. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
  236. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
  237. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
  238. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
  239. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/ensemble.py +0 -0
  240. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/grayswan/__init__.py +0 -0
  241. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/grayswan/analyzer.py +0 -0
  242. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/grayswan/utils.py +0 -0
  243. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/llm_analyzer.py +0 -0
  244. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/risk.py +0 -0
  245. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/shell_parser.py +0 -0
  246. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/toolshield_helpers.py +0 -0
  247. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
  248. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/settings/__init__.py +0 -0
  249. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/settings/acp_install_catalog.py +0 -0
  250. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/settings/acp_providers.py +0 -0
  251. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/settings/metadata.py +0 -0
  252. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/skills/__init__.py +0 -0
  253. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/skills/exceptions.py +0 -0
  254. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/skills/execute.py +0 -0
  255. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/skills/fetch.py +0 -0
  256. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/skills/installed.py +0 -0
  257. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/skills/skill.py +0 -0
  258. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/skills/trigger.py +0 -0
  259. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/skills/types.py +0 -0
  260. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/skills/utils.py +0 -0
  261. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/subagent/__init__.py +0 -0
  262. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/subagent/load.py +0 -0
  263. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/subagent/registry.py +0 -0
  264. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/subagent/schema.py +0 -0
  265. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/testing/__init__.py +0 -0
  266. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/testing/test_llm.py +0 -0
  267. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/__init__.py +0 -0
  268. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/finish.py +0 -0
  269. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
  270. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
  271. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/think.py +0 -0
  272. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
  273. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/client_tool.py +0 -0
  274. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/defaults.py +0 -0
  275. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/registry.py +0 -0
  276. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/schema.py +0 -0
  277. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/spec.py +0 -0
  278. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/tool/tool.py +0 -0
  279. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/__init__.py +0 -0
  280. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/async_executor.py +0 -0
  281. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/async_utils.py +0 -0
  282. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/cipher.py +0 -0
  283. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/command.py +0 -0
  284. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/datetime.py +0 -0
  285. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/deprecation.py +0 -0
  286. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/files.py +0 -0
  287. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/github.py +0 -0
  288. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/json.py +0 -0
  289. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/models.py +0 -0
  290. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/paging.py +0 -0
  291. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/path.py +0 -0
  292. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  293. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/redact.py +0 -0
  294. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/truncate.py +0 -0
  295. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/utils/visualize.py +0 -0
  296. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/workspace/__init__.py +0 -0
  297. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/workspace/base.py +0 -0
  298. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/workspace/local.py +0 -0
  299. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/workspace/models.py +0 -0
  300. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/__init__.py +0 -0
  301. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
  302. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/base.py +0 -0
  303. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
  304. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/workspace/repo.py +0 -0
  305. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands/sdk/workspace/workspace.py +0 -0
  306. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  307. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/requires.txt +0 -0
  308. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/top_level.txt +0 -0
  309. {openhands_sdk-1.49.4 → openhands_sdk-1.49.6}/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.6
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
@@ -55,6 +55,7 @@ from openhands.sdk.hooks import HookConfig, HookEventProcessor, create_hook_call
55
55
  from openhands.sdk.io import FileStore, LocalFileStore
56
56
  from openhands.sdk.llm import LLM, Message, TextContent, content_to_str
57
57
  from openhands.sdk.llm.auth.openai import create_subscription_llm_from_config
58
+ from openhands.sdk.llm.exceptions import LLMAuthenticationError
58
59
  from openhands.sdk.llm.llm import LLMCallContext
59
60
  from openhands.sdk.llm.llm_profile_store import LLMProfileStore
60
61
  from openhands.sdk.llm.llm_registry import LLMRegistry
@@ -2040,6 +2041,21 @@ class LocalConversation(BaseConversation):
2040
2041
  )
2041
2042
  )
2042
2043
  break
2044
+ except LLMAuthenticationError as e:
2045
+ with self._state:
2046
+ self._state.execution_status = ConversationExecutionStatus.ERROR
2047
+ self._on_event(
2048
+ ConversationErrorEvent(
2049
+ source="environment",
2050
+ code="LLMAuthenticationError",
2051
+ detail=(
2052
+ "Your LLM API key appears to be invalid or has expired."
2053
+ ),
2054
+ )
2055
+ )
2056
+ raise ConversationRunError(
2057
+ self._state.id, e, persistence_dir=self._state.persistence_dir
2058
+ ) from e
2043
2059
  except Exception as e:
2044
2060
  with self._state:
2045
2061
  self._state.execution_status = ConversationExecutionStatus.ERROR
@@ -2540,6 +2556,21 @@ class LocalConversation(BaseConversation):
2540
2556
 
2541
2557
  self._state.execution_status = ConversationExecutionStatus.PAUSED
2542
2558
  self._on_event(InterruptEvent())
2559
+ except LLMAuthenticationError as e:
2560
+ with self._state:
2561
+ self._state.execution_status = ConversationExecutionStatus.ERROR
2562
+ self._on_event(
2563
+ ConversationErrorEvent(
2564
+ source="environment",
2565
+ code="LLMAuthenticationError",
2566
+ detail=(
2567
+ "Your LLM API key appears to be invalid or has expired."
2568
+ ),
2569
+ )
2570
+ )
2571
+ raise ConversationRunError(
2572
+ self._state.id, e, persistence_dir=self._state.persistence_dir
2573
+ ) from e
2543
2574
  except Exception as e:
2544
2575
  with self._state:
2545
2576
  updated_agent_state = dict(self._state.agent_state)
@@ -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.
@@ -566,9 +566,10 @@ class HookExecutor:
566
566
  try:
567
567
  output_data = json.loads(result.stdout)
568
568
  if isinstance(output_data, dict):
569
- # Parse decision
570
- if "decision" in output_data:
571
- decision_str = output_data["decision"].lower()
569
+ # Non-string values represent no decision.
570
+ decision_value = output_data.get("decision")
571
+ if isinstance(decision_value, str):
572
+ decision_str = decision_value.lower()
572
573
  if decision_str == "allow":
573
574
  hook_result.decision = HookDecision.ALLOW
574
575
  elif decision_str == "deny":
@@ -28,6 +28,12 @@ from openhands.sdk.llm.message import (
28
28
  ThinkingBlock,
29
29
  content_to_str,
30
30
  )
31
+ from openhands.sdk.llm.meta_profile_store import (
32
+ MetaProfile,
33
+ MetaProfileClass,
34
+ MetaProfileLimitExceeded,
35
+ MetaProfileStore,
36
+ )
31
37
  from openhands.sdk.llm.router import RouterLLM
32
38
  from openhands.sdk.llm.streaming import (
33
39
  AsyncTokenCallbackType,
@@ -61,6 +67,10 @@ __all__ = [
61
67
  "LLMProfileLoader",
62
68
  "LLMProfileMutator",
63
69
  "LLMProfileStore",
70
+ "MetaProfile",
71
+ "MetaProfileClass",
72
+ "MetaProfileLimitExceeded",
73
+ "MetaProfileStore",
64
74
  "RouterLLM",
65
75
  "RegistryEvent",
66
76
  # Messages
@@ -96,6 +96,7 @@ from openhands.sdk.llm.exceptions import (
96
96
  LLMNoResponseError,
97
97
  is_prompt_cache_too_small,
98
98
  is_quota_exhaustion_error,
99
+ looks_like_auth_error,
99
100
  map_provider_exception,
100
101
  )
101
102
 
@@ -661,6 +662,19 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
661
662
  # a result, so the read/check and store are kept atomic. ClassVar (shared);
662
663
  # critical sections are tiny and never cover network I/O.
663
664
  _runtime_metadata_lock: ClassVar[threading.Lock] = threading.Lock()
665
+ # Optional callback that returns a freshly-resolved API key. When set, an
666
+ # authentication failure (HTTP 401, e.g. a rotated/healed managed proxy key
667
+ # that LiteLLM reports as ``token_not_found_in_db``) triggers a single
668
+ # re-resolve of the key followed by one retry of the call. This is a
669
+ # near-term mitigation for stale credentials reaching a sandbox runtime
670
+ # agent; the durable fix is reference-only credential delivery (#4288).
671
+ # Held as a PrivateAttr so it is never serialized into conversation state.
672
+ _api_key_refresh_hook: Callable[[], str | SecretStr | None] | None = PrivateAttr(
673
+ default=None
674
+ )
675
+ # Recursion guard: set on the refreshed copy so a still-failing key does not
676
+ # loop. One refresh + one retry per call chain.
677
+ _auth_refresh_attempted: bool = PrivateAttr(default=False)
664
678
  model_config: ClassVar[ConfigDict] = ConfigDict(
665
679
  extra="ignore", arbitrary_types_allowed=True
666
680
  )
@@ -1011,6 +1025,72 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
1011
1025
  raise mapped from error
1012
1026
  raise
1013
1027
 
1028
+ # =========================================================================
1029
+ # Authentication refresh (refresh-on-401)
1030
+ # =========================================================================
1031
+ def set_api_key_refresh_hook(
1032
+ self, hook: Callable[[], str | SecretStr | None] | None
1033
+ ) -> None:
1034
+ """Register a callback that re-resolves this LLM's API key on a 401.
1035
+
1036
+ When a completion/responses call fails with an authentication error,
1037
+ the hook is invoked once to obtain a fresh key; if it returns a new
1038
+ value the call is retried a single time with that key. This mitigates
1039
+ stale managed/proxy credentials reaching a sandbox runtime agent (e.g.
1040
+ after a server-side managed-key rotation or heal), where the first call
1041
+ would otherwise fail with ``401 token_not_found_in_db``.
1042
+
1043
+ The hook must be non-blocking / cheap; on the async paths it is invoked
1044
+ in a worker thread. It is stored as a private attribute and is never
1045
+ serialized into conversation state.
1046
+ """
1047
+ self._api_key_refresh_hook = hook
1048
+
1049
+ def _resolve_refreshed_api_key(self, error: Exception) -> SecretStr | None:
1050
+ """Return a fresh API key to retry with, or ``None`` to skip refresh.
1051
+
1052
+ Returns ``None`` unless every condition holds: the error looks like an
1053
+ authentication failure, a refresh hook is configured, no refresh has
1054
+ already been attempted in this call chain, this LLM uses ``api_key``
1055
+ auth, and the hook yields a key that differs from the current one.
1056
+ """
1057
+ if self._auth_refresh_attempted:
1058
+ return None
1059
+ if self._api_key_refresh_hook is None:
1060
+ return None
1061
+ if self.auth_type != "api_key":
1062
+ return None
1063
+ if not looks_like_auth_error(error):
1064
+ return None
1065
+ try:
1066
+ new_key = self._api_key_refresh_hook()
1067
+ except Exception:
1068
+ # A flaky hook must not mask the original authentication error;
1069
+ # log and skip the refresh so the caller sees the real 401.
1070
+ logger.warning(
1071
+ "API key refresh hook raised; skipping refresh and "
1072
+ "surfacing the original authentication error.",
1073
+ exc_info=True,
1074
+ )
1075
+ return None
1076
+ if new_key is None:
1077
+ return None
1078
+ new_secret = new_key if isinstance(new_key, SecretStr) else SecretStr(new_key)
1079
+ if not new_secret.get_secret_value():
1080
+ return None
1081
+ current = self._get_api_key_value()
1082
+ if current is not None and current == new_secret.get_secret_value():
1083
+ # A refresh that returns the same (still-rejected) key is useless;
1084
+ # skip the retry and surface the original error.
1085
+ return None
1086
+ return new_secret
1087
+
1088
+ def _auth_refreshed_llm(self, new_api_key: SecretStr) -> LLM:
1089
+ """Copy this LLM with a refreshed key and the recursion guard set."""
1090
+ refreshed = self.model_copy(update={"api_key": new_api_key})
1091
+ refreshed._auth_refresh_attempted = True
1092
+ return refreshed
1093
+
1014
1094
  # =========================================================================
1015
1095
  # Shared helpers for completion / acompletion / responses / aresponses
1016
1096
  # =========================================================================
@@ -1668,6 +1748,21 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
1668
1748
  on_token=on_token,
1669
1749
  **_caller_kwargs,
1670
1750
  )
1751
+ # If the credential was rejected (401) and a refresh hook can supply
1752
+ # a fresh key, re-resolve it and retry the call exactly once.
1753
+ refreshed_key = self._resolve_refreshed_api_key(e)
1754
+ if refreshed_key is not None:
1755
+ logger.warning(
1756
+ "Authentication error; refreshing API key and retrying once."
1757
+ )
1758
+ return self._auth_refreshed_llm(refreshed_key).completion(
1759
+ messages,
1760
+ tools,
1761
+ add_security_risk_prediction=add_security_risk_prediction,
1762
+ on_token=on_token,
1763
+ call_context=call_context,
1764
+ **_caller_kwargs,
1765
+ )
1671
1766
  return self._handle_error(
1672
1767
  e,
1673
1768
  lambda fb: fb.completion(
@@ -1772,6 +1867,24 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
1772
1867
  on_token=on_token,
1773
1868
  **_caller_kwargs,
1774
1869
  )
1870
+ # If the credential was rejected (401) and a refresh hook can supply
1871
+ # a fresh key, re-resolve it (off the event loop) and retry once.
1872
+ loop = asyncio.get_running_loop()
1873
+ refreshed_key = await loop.run_in_executor(
1874
+ None, self._resolve_refreshed_api_key, e
1875
+ )
1876
+ if refreshed_key is not None:
1877
+ logger.warning(
1878
+ "Authentication error; refreshing API key and retrying once."
1879
+ )
1880
+ return await self._auth_refreshed_llm(refreshed_key).acompletion(
1881
+ messages,
1882
+ tools,
1883
+ add_security_risk_prediction=add_security_risk_prediction,
1884
+ on_token=on_token,
1885
+ call_context=call_context,
1886
+ **_caller_kwargs,
1887
+ )
1775
1888
  # Fallback is synchronous; cast the token callback since the
1776
1889
  # fallback LLM's sync path accepts TokenCallbackType.
1777
1890
  _fb_token = cast("TokenCallbackType | None", on_token)
@@ -1938,6 +2051,23 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
1938
2051
  on_token=on_token,
1939
2052
  **_caller_kwargs,
1940
2053
  )
2054
+ # If the credential was rejected (401) and a refresh hook can supply
2055
+ # a fresh key, re-resolve it and retry the call exactly once.
2056
+ refreshed_key = self._resolve_refreshed_api_key(e)
2057
+ if refreshed_key is not None:
2058
+ logger.warning(
2059
+ "Authentication error; refreshing API key and retrying once."
2060
+ )
2061
+ return self._auth_refreshed_llm(refreshed_key).responses(
2062
+ messages,
2063
+ tools,
2064
+ include,
2065
+ store,
2066
+ add_security_risk_prediction=add_security_risk_prediction,
2067
+ on_token=on_token,
2068
+ call_context=call_context,
2069
+ **_caller_kwargs,
2070
+ )
1941
2071
  return self._handle_error(
1942
2072
  e,
1943
2073
  lambda fb: fb.responses(
@@ -2115,6 +2245,26 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
2115
2245
  on_token=on_token,
2116
2246
  **_caller_kwargs,
2117
2247
  )
2248
+ # If the credential was rejected (401) and a refresh hook can supply
2249
+ # a fresh key, re-resolve it (off the event loop) and retry once.
2250
+ loop = asyncio.get_running_loop()
2251
+ refreshed_key = await loop.run_in_executor(
2252
+ None, self._resolve_refreshed_api_key, e
2253
+ )
2254
+ if refreshed_key is not None:
2255
+ logger.warning(
2256
+ "Authentication error; refreshing API key and retrying once."
2257
+ )
2258
+ return await self._auth_refreshed_llm(refreshed_key).aresponses(
2259
+ messages,
2260
+ tools,
2261
+ include,
2262
+ store,
2263
+ add_security_risk_prediction=add_security_risk_prediction,
2264
+ on_token=on_token,
2265
+ call_context=call_context,
2266
+ **_caller_kwargs,
2267
+ )
2118
2268
  _fb_token = cast("TokenCallbackType | None", on_token)
2119
2269
  return await self._ahandle_error(
2120
2270
  e,
@@ -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."""